[comp.lang.eiffel] Eiffel cleanup #2: Doing away with void references

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