Path: utzoo!utgpu!news-server.csri.toronto.edu!cs.utexas.edu!sun-barr!olivea!uunet!mcsun!ukc!pyrltd!tetrauk!rick From: rick@tetrauk.UUCP (Rick Jones) Newsgroups: comp.lang.eiffel Subject: Re: Continued discussion of object model in Eiffel Keywords: Eiffel c++ object Message-ID: <1095@tetrauk.UUCP> Date: 14 Feb 91 10:50:22 GMT References: <70548@microsoft.UUCP> Reply-To: rick@tetrauk.UUCP (Rick Jones) Organization: Tetra Ltd., Maidenhead, UK Lines: 195 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)