bertrand@eiffel.UUCP (Bertrand Meyer) (01/17/90)
A planned change for the handling of ``void'' references in Eiffel ------------------------------------------------------------------ This is the second of a sequence of postings describing the changes planned for version 3 of Eiffel. These are cleanup changes and do not affect anything fundamental. This is particularly true of the change described here, whose concrete effect on the actual form of Eiffel software is extremely small, although its conceptual impact (in terms of elegance of the overall language structure and type system) is significant. I am cross-posting this particular message to comp.object because of its Eiffel-independent components (influence on the type theory of object-oriented programming). Acknowledgments --------------- The key idea for the improvement described here was contributed by John Sarkela, director of training at Interactive Software Engineering. As usual, the rest of the Eiffel group at Interactive provided essential help and feedback. ---------------------------------------------------------------------- |WARNING: The change described here is planned for version 3 of the | |Eiffel environment, for which Interactive will not release a version | |until late 1990. | | | |Any change in the language supported by Interactive's tools | |will be accompanied by CONVERSION TOOLS to translate ``old'' syntax | |into new. Programmers will NOT need to perform any significant work | |to update existing Eiffel software. | | | |This posting is made solely for the purpose of informing the Eiffel | |community about ongoing developments. Although the posting has been | |preceded by careful reflection and internal discussions within | |Interactive, we make no commitment at this point that the features | |described here will actually be included, and, if they are, that | |their final form will be the exact one shown below. | ---------------------------------------------------------------------- Purpose of the change. ---------------------- The purpose of the change is to solve several problems at once through an improved syntax and semantics for what is currently known as ``void references'' in Eiffel. The problems and their solution affect the conceptual cleanliness of the language and its type system more than the specifics of Eiffel programming. Problem 1: In current Eiffel, there are two kinds of values for entities of class types: ``void reference'' or reference to an object. (In contrast, entities of the other kind of type, expanded, have values which are always objects.) The need to precede any semantic discussion by a qualifier of the form ``if the reference is not void, then...'' is a nuisance. Problem 2: If a system attempts to apply a feature to an entity whose value is a void reference, this cannot be executed and triggers an exception. Such a situation is inevitable (it may be the case that in an object-oriented theory of computation the presence of an ``feature application to void reference'' would be the basic undecidable problem); it should, however, be treated in a more general framework, such as type checking. Problem 3: In some special cases, it may be useful to permit routines that are applicable to void references. A typical example is procedure print, in the universal class ANY; this is applicable to objects of any type, so that a.print will print a representable form of a adapted to the type of a. There are good arguments for making such a procedure applicable to void references: if you are adding print instructions to a class for purposes of debugging, you probably want a.print to do nothing, silently, if a is void, rather than produce a run-time exception. Although we have experimented with various techniques for specifying that a routine is applicable to void references, none seems to be really satisfactory. The problem is that Eiffel routines are not just ``pieces of code'' but the implementation of a *contract*, specified by a precondition and a postcondition. The pre- and postconditions become extremely complex to express if you take into account the special treatment for the case when the target is void. Problem 4: Normal feature applications a.f (...) act on the object associated with reference a, and cannot change that reference itself. There are, however, four exceptions: Create, Clone, Forget and Void. Such irregularity is unpleasant. The case of Create and Clone was addressed in a previous posting (which, by oversight, forgot to list Void among the culprits). This leaves Forget, whose effect is to make the target void, and Void, a boolean-valued feature which tells whether the target is void or not. (By default, all entities have a void value on creation.) The universal classes (reminder) -------------------------------- The following is just a reminder which is necessary to understand the remainder of the discussion. The Eiffel class system includes two special classes: ANY and HERE. HERE inherits from ANY, and any programmer-defined class inherits from HERE (without explicit specification by the programmer) ANY contains a few universally useful features: print (mentioned about), io (standard input/output) etc. HERE is empty as delivered, so that it just repeats the features of ANY. Programmers are free to add features (normally routines) to it, however, whereas ANY is meant to be left unchanged. This makes it possible to endow every class written in a certain environment with new universal features local to that environment (a project, a department, a company). The language change ------------------- The structure of the class system is now the following: ANY ^ | | | HERE ^ ^ ^ | | | | | | | | | | | | ............................................................... ............................................................... ............................................................... ............... All other classes .......................... ............................................................... ............................................................... ^ ^ ^ | | | | | | | | | | | | NONE In other words, there is a new class NONE, which (as the picture tries to show within the constraints of vertical-horizontal-only graphics) inherits from every class. Those of you who like lattices and complete partial orders will be happy to see that the multiple inheritance lattice of Eiffel has now at last gained its ``bottom''. (Others should ignore this paragraph.) This means that whenever any Eiffel programmer anywhere in the world writes a new class, he must write to the maintainers of class NONE in sunny Goleta (California) so that we can update its inheritance clause; in so doing we must remove name clashes as necessary. The previous sentence contains two inaccuracies: - We just got our first real rain in more than a year, which is good because we would all have died of thirst if it had not been for the Zinfandel from the Santa Ynez wineries. - You do not really need to tell us about your new classes (although we are always happy to know). The effect is conceptually that of NONE inheriting from everybody else, but it does not have to be that way physically. As alert readers will have guessed, there is no more ``void'' value. Every value of class type, without exception, now refers to an object. What used to be called a void value is now a reference to an instance of class NONE. It is enough to have one instance of this type around during the execution of any Eiffel system since (as will be seen next) almost nothing may be done to it. Indeed, we may consider that one such instance is created at system execution; any entity of class type is initialized to a reference to that object (This specification may be taken verbatim by an implementer of Eiffel; or it may be just used as a metaphor, the implementation being different. As noted below, the current implementation of Eiffel does not need to be changed for the metaphor to hold conceptually.) Class ANY now contains a function null: NONE is once end which returns a reference to the single meaningful instance of NONE. Instead of writing a.Forget, then. you now write (as in Pascal!) a := null The test a.Void may in principle be written a == null but it is better to keep the old form a.void, which is now now a normal feature call to the function void: BOOLEAN is do ... end in class ANY. Note that ``void'' and ``null'' need not be actually implemented as functions. For obvious performance reasons, a smart compiler may (and probably should) recognize the corresponding feature calls and treat them in-line - using exactly the same code as in current Eiffel. Actually, one of the nice aspects of this change is that the current implementation (where void values are references with a special value, such as 0 or -1) remains totally valid. The theory changes, but the practice may remain the same. Only a small syntactical change is needed: replacing calls to Forget by assignments, as indicated above. Obviously, this change will be performed by automatic tools, not by the programmers. There is virtually no change in semantics since almost no operation is exported by NONE, so that a.f will usually be incorrect if a is void. Why ``almost'' and ``usually''? Well, class NONE is not as empty as you may think at first. The theoretical form of that class is class NONE export repeat HERE inherit ... All classes ever written by anyone ... end [Each one among these ``all classes'' inherits from HERE.] For those readers who haven't read ``Eiffel: The Language'', the ``repeat HERE'' clause means that NONE exports everything that its ancestor HERE exports. This includes basic features such as void, print, null, out, io etc. (By the way, it is hardly unlikely that anyone would want to declare an attribute in HERE since every object will carry it. This is not forbidden, but is a big responsibility. In most cases HERE should only contain routines, which do not take up any space in objects, and whose code is removed by the automatic optimizer if not used.) No you see how the problem of routines applicable to void references is addressed: such routines occur rarely enough, and are special enough, that they should be added to your local version of HERE. One of the remarkable aspects of this structure is that the problem of ``feature application to void references'' becomes a special case of type checking! Those readers who remember an earlier, lengthy posting on static type checking in Eiffel will see that the present change puts the whole discussion in a more general perspective. A note on conversion of existing software ----------------------------------------- Conversion of existing Eiffel classes will be straightforward and, as mentioned above, done automatically. (Actually, a simple lexical translator, or even a 3-line sed script could do it.) -- Bertrand Meyer bertrand@eiffel.com
kim@helios.enea.se (Kim Wald`n) (01/20/90)
In article <227@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes: > No you see how the problem of routines applicable to void > references is addressed: such routines occur rarely enough, and > are special enough, that they should be added to your local version > of HERE. And also exported in HERE, I suppose? When trying to figure out how this would work, I found that the universal model, as described in the reference manual, is not very clear: 1. p. 171 states: Every class which has no Inheritance clause is understood to have an Inheritance clause of the form inherit HERE Does this mean that a non-empty inheritance clause in the topmost class, like class TOPMOST export ... inherit ANY rename out as basic_out feature ... also, as a side-effect, hides the local features of HERE from TOPMOST and all its descendants? If so, a standard way of renaming features from ANY would rather be: class TOPMOST export ... inherit HERE rename out as basic_out feature ... Or perhaps the text should be interpreted: Every class which has no inheritance clause (or only inherits from ANY) is... By the way, has anybody actually tested a non-empty class HERE? I tried to check out its export behavior by adding a feature: class HERE inherit ANY feature hi is do io.putstring("hi"); io.new_line; end; end -- class HERE But then my simple hello-program wouldn't compile. "es" complained: "basic: Export clause lists undefined feature: hi" and stopped. Trying various explicit exports of "hi" didn't seem to help. 2. p. 173 argues that the features of class ANY are always available to all classes, both in qualified and unqualified form, since ANY uses selective export to ANY itself. Thus, "y := p.out" is valid in any class C (for non-void "p"). This seems a very strange line of reasoning to me. First, if "p" is of type P, it should be the export clause of P that decides whether "out" is remotely available to C, and not the export clause of any proper ancestor, like ANY. Second, selective export is defined on p. 65 as a simple restriction to general export, so "export X {ANY}" should mean exactly the same as "export X", so the section doesn't make sense. (Am I missing some subtle point here, or is the manual text just the sunny effect of repeated inheritance from class Zinfandel :-) 3. One might argue that the theoretical form class NONE export repeat HERE ... is not quite correct, since the "repeat" clause would only be valid where HERE is a direct parent of NONE (p. 64) Besides this, the proposed solution looks elegant, as always. -- Kim Walden Enea Data, Sweden kim@enea.se
sommar@enea.se (Erland Sommarskog) (01/21/90)
Bertrand Meyer (bertrand@eiffel.UUCP) writes: > - You do not really need to tell us about your new classes (although > we are always happy to know). The effect is conceptually that > of NONE inheriting from everybody else, but it does not have to > be that way physically. You don't say it somewhere, but I suppose that inherit NONE is illegal. Or does this mean that I inherit from every class in the world? Computer telepathy? :-) >Instead of writing a.Forget, then. you now write (as in Pascal!) > > a := null Almost. "Null" is Ada. In Pascal, it's "nil". >The test a.Void may in principle be written > > a == null Stupid question department: this is a typing error, isn't it? Or is there really a "==" in Eiffel? Doesn't OOSC discuss why a language should not have both "=" and "==" to avoid casual mistakes? -- Erland Sommarskog - ENEA Data, Stockholm - sommar@enea.se Unix is a virus.
jos@cs.vu.nl (Jos Warmer) (01/23/90)
In article <668@enea.se> sommar@enea.se (Erland Sommarskog) writes: > >You don't say it somewhere, but I suppose that > > inherit NONE > >is illegal. Or does this mean that I inherit from every class in >the world? Computer telepathy? :-) > You are right, this is illegal. The inheritance graph is a Directed Acyclic Graph (DAG) and because NONE is a descendant from each class in the universe, no class can be a descendant from NONE. Note that this restriction is already defined within the current eiffel definition, so no special case is needed for NONE. Jos Warmer jos@cs.vu.nl ...uunet!mcvax!cs.vu.nl!jos PS. The compiler detects cycles in the inheritance graph and gives the following error message: Inheritance relation may not contain any cycles. --
nicwi@isy.liu.se ( Niclas Wiberg) (01/24/90)
bertrand@eiffel.UUCP (Bertrand Meyer) writes: >... > > There is virtually no change in semantics since almost no operation >is exported by NONE, so that > > a.f > >will usually be incorrect if a is void. > >... > > One of the remarkable aspects of this structure is that the >problem of ``feature application to void references'' becomes a >special case of type checking! Those readers who remember an earlier, >lengthy posting on static type checking in Eiffel will see that >the present change puts the whole discussion in a more general >perspective. I don't remember that earlier posting about type checking, so please excuse me if I've got something wrong. But to me it seems that all classes must fullfill *all* the contracts of *all* of its ancestors. It shouldn't matter whether you export the inherited contracts or not. For example, consider these two classes: class PARENT export some_feature feature some_feature is ... end end -- class PARENT class CHILD export inherit PARENT feature end -- class CHILD Assume these declarations: p: PARENT; c: CHILD If you create a CHILD object c.Create you can of course not apply the feature some_feature to c c.some_feature -- This is incorrect but if you make the assignment p := c then this should be perfectly legal: p.some_feature Of course you can't require the class NONE to fullfill all the contracts of its ancestors, but then what's the point in the extension? You still have a special case, which must be treated differently. -- ---------------------------------------------------------------------- Niclas Wiberg nicwi@isy.liu.se Dept. of EE Linkoping University Sweden