[comp.lang.eiffel] Continued discussion of object model in Eiffel

marklan@microsoft.UUCP (Mark LANGLEY) (02/08/91)

The protection issue of objects that has been discussed lately
(namely, C++ public-protected-private interface, vs Eiffel's idea 
that you access all your baseclass fields.) gets at another key 
distinction in the Eiffel object philosophy vs the C++ object 
philosophy.

In Eiffel, when you define a class it is necessary that you be 
able to get your hands on all the class's fields, because that's 
the only way you can initialize them!  (Well you *could* rename 
all the features, and get at them that way -- more on this in a sec.)

On the other hand, the invariant in C++ is that "C++ will hand you 
no object before it's initialized."  Alternatively, Eiffel hands 
you objects raw, and just says "Thank you for your support."

It's interesting that C++ which has the avowed philosophy of 
"never add run-time cost" has the more expensive object model.  
For example, if you inherit a lot of stuff which has constructors, 
you can find that you spend a lot of time waiting for things to 
initialize.  (Now obviously you can beat the clock a lot of time -- 
don't declare things you don't want initialized, etc.  But the 
amount of time spent doing unexpected initializations of temporaries, 
gratuitous copying, initilizing locals, and globals (the latter all at 
program start-up, whether you will ever need them or not) is non-trivial 
in big programs.)

This extra work that C++ pledges to do for you also leads to another 
awkward situation: ctor-initialization lists. i.e.  When you have :

class foo : public base1, public base2, public OhRats  {
	const int OhRats;
	foo(p1) : base1(10), base2(p1), OhRats(0) { };
	};

The constructor initialization list offers three surprises.  1) The
order in which things are initialized is controlled by the language,
and may bear no resemblence to their order in the list.  2) Your 
choices on how things get initiliazed are limited.  You can't for 
example say "initialize base1, then call function f, and depending on 
what it returns initialize base2 with an integer, otherwise initialize 
it with a null string". 3) you come to grief if you have a baseclass
with the same name as a member.

Since Eiffel requires that you do your own initialization of baseclasses,
I would like to see Eiffel extended to allow invocation of features without 
having to rename them into the current class; renaming seems awkward, error 
prone, and misleading.  I want to be able to say (yes, I know about the new
create syntax -- more on that in a minute)

	inherit base1; base2
	feature
	create is
	do
		current.base1$create(10);	-- baseclass '$' feature
		--
		-- whatever other stuff I want to do...
		--
		current.base2$create(20);	-- baseclass '$' feature
	end

Now about that new creation syntax: I don't think I like it.  The
ISE people were good enough to repost the material that they sent
out last year sometime on the new create syntax, and I saw a sneak
preview in a draft of the manual, but it seems misguided.

In the next major release of Eiffel from ISE you will be able to say:

	local
		x :someclass;
	do
		x!
	end

In the first place, forcing the user to do x! is a cruel hoax.  Creation
in Eiffel is a two phase process anyway -- the system part initializes
and creates an actual object, and then the user fills it in.  Decoupling
the two phases was a good idea, but why not go all the way?  No more void 
references!  always do the system initialization before an object is needed.  
If you need to tell if an object has been initialized or not, then have a 
feature "init" or something that gets set to non-zero when the create method 
runs.  (Has eliminating void references been been suggested before? it seems 
like such an obvious thing...)

Version 3's new syntax allows you to call a descendent (not ancestor --
that's what I thought too) function on the current object under
construction as follows.
	
	x!DescendentFeature!mycreate(10,20);

The idea of calling a descendent class is barbaric:  It encourages fuzzy 
reasoning about the type system; It's imprecise and it's error-prone.  You 
can't call a descendent function at any other time in an object's life, why 
should it be legal at creation time?

Mark

sakkinen@tukki.jyu.fi (Markku Sakkinen) (02/14/91)

In article <70548@microsoft.UUCP> marklan@microsoft.UUCP (Mark LANGLEY) writes:
> ...
>distinction in the Eiffel object philosophy vs the C++ object 
>philosophy.
> ...
>It's interesting that C++ which has the avowed philosophy of 
>"never add run-time cost" has the more expensive object model.  
>For example, if you inherit a lot of stuff which has constructors, 
>you can find that you spend a lot of time waiting for things to 
>initialize.  [...]

And when you have "ended the programme" (exited 'main'),
it can still spend a lot of time waiting for things to finalise
(calling the destructors of objects).

However, I think that the possibility to define constructors and
destructors is an advantage of C++ over most other OOPL's.
In some other design decisions of that language, run-time efficiency
has indeed triumphed over object orientation, with sad results.
(But enough with that, this group seems to be comp.lang.eiffel.)

> ...
>Since Eiffel requires that you do your own initialization of baseclasses,
>I would like to see Eiffel extended to allow invocation of features without 
>having to rename them into the current class; renaming seems awkward, error 
>prone, and misleading.  [...]

Agreed!

> ...
>Now about that new creation syntax: I don't think I like it.  [...]

I think the reasons for the change were very logical.

>In the next major release of Eiffel from ISE you will be able to say:
>
>	local
>		x :someclass;
>	do
>		x!
>	end
>
>In the first place, forcing the user to do x! is a cruel hoax.  [...]
> ...
> [...]  (Has eliminating void references been been suggested before? it seems 
>like such an obvious thing...)

Keep in mind that variables and objects are totally distinct things
in languages such as Eiffel!  Often the first thing done with a variable
is assigning an existing object to it.  The automatic creation of a new
object would then be labour lost, and the amount of labour may not be
negligible if the object is large and complex.

A more serious difficulty for this proposal appears with directly
or indirectly recursive classes.  How should automatic initialisation
be arranged in such cases?  Example (syntax may not be 100% correct):

class Person export ... feature
        mother: Person;  father: Person;  spouse: Person;
        children: Person_set;
        ...

Note also that there may be a valid meaning for a variable staying Void:
in this example e.g. that a person really has no spouse.

>Version 3's new syntax allows you to call a descendent (not ancestor --
>that's what I thought too) function on the current object under
>construction as follows.
>	
>	x!DescendentFeature!mycreate(10,20);
>
>The idea of calling a descendent class is barbaric:  It encourages fuzzy 
>reasoning about the type system; It's imprecise and it's error-prone.  You 
>can't call a descendent function at any other time in an object's life, why 
>should it be legal at creation time?
> ...

That does sound ugly indeed!

Markku Sakkinen
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
          SAKKINEN@FINJYU.bitnet (alternative network address)

rick@tetrauk.UUCP (Rick Jones) (02/14/91)

In article <70548@microsoft.UUCP> marklan@microsoft.UUCP (Mark LANGLEY) writes:
>The protection issue of objects that has been discussed lately
>(namely, C++ public-protected-private interface, vs Eiffel's idea 
>that you access all your baseclass fields.) gets at another key 
>distinction in the Eiffel object philosophy vs the C++ object 
>philosophy.
>
> [various comments and criticism about C++'s handling of constructors, all of
> which I agree with (although I never realised it was quite *that* bad :-)]
>
>Since Eiffel requires that you do your own initialization of baseclasses,
>I would like to see Eiffel extended to allow invocation of features without 
>having to rename them into the current class; renaming seems awkward, error 
>prone, and misleading.  I want to be able to say (yes, I know about the new
>create syntax -- more on that in a minute)
>
>	inherit base1; base2
>	feature
>	create is
>	do
>		current.base1$create(10);	-- baseclass '$' feature
>		--
>		-- whatever other stuff I want to do...
>		--
>		current.base2$create(20);	-- baseclass '$' feature
>	end

The problem with this is that you still have to resolve a name clash anyway.
Currently, the Create feature is special in that it is not inherited unless
it's renamed.  That is actually inconsistent, and the new creation syntax will
eliminate it.  Therefore, inheriting from two classes which have features of
the same name (and which are not the same actual feature) requires that at
least one is renamed.  If you inherit only one feature of a specific name,
redefine it, but still want to call the original, I can see some merit in your
suggestion.

>Now about that new creation syntax: I don't think I like it.  The
>ISE people were good enough to repost the material that they sent
>out last year sometime on the new create syntax, and I saw a sneak
>preview in a draft of the manual, but it seems misguided.
>
>In the next major release of Eiffel from ISE you will be able to say:
>
>	local
>		x :someclass;
>	do
>		x!
>	end
>
>In the first place, forcing the user to do x! is a cruel hoax.  Creation
>in Eiffel is a two phase process anyway -- the system part initializes
>and creates an actual object, and then the user fills it in.  Decoupling
>the two phases was a good idea, but why not go all the way?  No more void 
>references!  always do the system initialization before an object is needed.  
>If you need to tell if an object has been initialized or not, then have a 
>feature "init" or something that gets set to non-zero when the create method 
>runs.  (Has eliminating void references been been suggested before? it seems 
>like such an obvious thing...)

I can't see what eliminating void references would achieve.  You seem to be
suggesting that all references should be non-void, but some could be
uninitialised.  But if an object is uninitialised, you can't expect to use any
of its features.  It is a requirement that the invariant holds both on entry
and exit to a routine (enforced if full assertion checking is on), so an
attempt to use an uninitialised object is illegal and with assertion checking
will cause an exception.  Ditto a void reference!  If you need to test for
initialised, you might as well test for void, which is in fact simpler.  A void
reference is also very valuable, since it implies "there is no information at
this point" as opposed to "the information here is zero".  I certainly use a
lot of logic which is totally dependent on this distinction.

For classes which do not have explicit create routines (i.e. their
uninitialised objects are correct), it is not unreasonable to want to avoid
explicit creation in some circumstances.  In many cases, use of expanded types
in Eiffel will cater for this.  Any class which has no create routine, or one
which takes no arguments, may have an instance declared as expanded.  The
effect is primarily that the object is created, and initialised if it has a
create routine, as soon as it comes into scope.  Destruction is handled by the
garbage collector in the normal way.  With a local expanded declaration this is
the only difference;  if the declaration is an attribute of a class, then the
object is also embedded into its parent object, and is not a reference.

Question:  how is the new creation syntax going to operate with expanded types?
When there is no creation routine at all, there is no problem.  If there is
only one which takes no arguments, that is also obvious.  But if there is more
than one creation routine without arguments (seems unlikely, but not illegal),
which one will be called for an expanded type?

>Version 3's new syntax allows you to call a descendent (not ancestor --
>that's what I thought too) function on the current object under
>construction as follows.
>	
>	x!DescendentFeature!mycreate(10,20);
>
>The idea of calling a descendent class is barbaric:  It encourages fuzzy 
>reasoning about the type system; It's imprecise and it's error-prone.  You 
>can't call a descendent function at any other time in an object's life, why 
>should it be legal at creation time?

I think you've misunderstood the concept.  You don't call a descendant
*feature*, you name a descendant *class*.  The purpose is to solve the
following sort of problem:

I have a class A, from which inherits A1, A2, A3 (etc).  I need a function
whose return class is A, but where the actual instance is one of its
descendants, chosing according to the argument given to the function (this is
not academic, I have several instances of this sort of code).  Currently I have
to write:

	class_A (type: INTEGER): A is
	local
		a1: A1 ;
		a2: A2 ;
		a3: A3 ;
		-- etc
	do
		inspect
			type
		when 1 then
			a1.Create ; Result := a1 ;
		when 2 then
			a2.Create ; Result := a2 ;
		when 3 then
			a3.Create ; Result := a3 ;
		-- etc
		end
	end

All other differentiation between A1, A2, A3 is handled by dynamic binding, but
there is always one point where you have to make the explicit decision which
type to create in the first place.  One effect of the new style of creation is
to get rid of all those local declarations, and the separate assignments to
Result.  It reduces to:

	class_A (type: INTEGER): A is
	do
		inspect
			type
		when 1 then
			Result!A1!Create ;
		when 2 then
			Result!A2!Create ;
		when 3 then
			Result!A3!Create ;
		-- etc
		end
	end

In fact if you didn't have this, *and* you wanted all objects created by
default, in the first version you would be allocating space for a1, a2, & a3
when only one was ever going to be used!

This solution could go even further, if class names could be variables.
If CLASS were a data type, how about this:

	class_A (type: INTEGER): A is
	local
		class_type: CLASS ;
	do
		inspect
			type
		when 1 then
			class_type := A1 ;
		when 2 then
			class_type := A2 ;
		when 3 then
			class_type := A3 ;
		-- etc
		end
		Result!class_type!Create ;
	end

This is a more obvious improvement if the Create routine takes several
arguments, which otherwise have to be repeated for each case.  It would also
allow construction of an ARRAY [CLASS], to be done once, so that "type" above
could look up a type as an index.  This would reduce to:

	Result!class_list.item (type)!Create ;

Given that at runtime classes are identified simply as integers, this shouldn't
be at all difficult.  What other opinions are there on classes as variables?
(sudden thought from me: could wreak havoc with type-checking!).

Mark has raised a basic point of syntax, though.  In a call of the form:

	a: A ;

	a!A1!routine

is "routine" defined in relation to the interface of class A or of class A1?

-- 
Rick Jones, Tetra Ltd.  Maidenhead, Berks, UK
rick@tetrauk.uucp
Journalist:	What would you do if you had Saddam Hussein on your couch?
Psychiatrist:	I'd make a run for the door	(BBC's "Panorama" - 11/2/91)