[comp.lang.eiffel] Operations on void references

gjc@batserver.cs.uq.oz (Cumming) (09/08/89)

I am interested in knowing why routines may not operate on void references. An
obvious example is the traversal of a null tree.

Whereas I would like to write

	tree.traverse

I must actually write

	if not tree.void then
		tree.traverse

This seems unnecessary and very inconvenient in certain circumstances.

G.J. Cumming

bertrand@eiffel.UUCP (Bertrand Meyer) (09/09/89)

From <1470@moondance.cs.uq.oz>, gjc@batserver.cs.uq.oz (Cumming):
> I am interested in knowing why routines may not operate on void references.
> An obvious example is the traversal of a null tree.
> 
> Whereas I would like to write
> 
>     tree.traverse
> 
> I must actually write
> 
>     if not tree.void then
>         tree.traverse
> 
> This seems unnecessary and very inconvenient in certain circumstances.
> 

    I have recently given some thought to this issue and have indeed
begun to design a simple extension that addresses this problem and may
be in version 3. But let me first sharpen the analysis a little.

The behavior suggested by the above message should NOT be the default one.
If a client is trying to apply a feature to an object that does not exist,
it is often a mistake.  The general principle in Eiffel is that when a
mistake occurs the system should ``shout loudly'' and interrupt execution 
(that is to say, trigger an exception) rather than do nothing and
proceed silently to the rest of the day's business.

For further confirmation that quiet ignorance of a call on a non-existing
object is generally improper, consider correctness concerns (which, by the
way, are too often absent from discussions on programming languages and
programming constructs). The caller of

    x.f

is expecting that, on return, the postcondition of f will be satisfied.
Now if the policy is to do nothing when x is void, this will not in
general be true, and the client may continue its computation on the basis
of incorrect assumptions.

As a minor comment, the example given by Mr. (?) Cumming is not that
convincing since an empty tree will usually be represented by an object,
so that ``tree'' in the above code will be a non-void reference to
an object on which a function such as ``empty'' yields true -
not a void reference.
(The choice of the word ``void'' does entail some potential confusions,
for which I must take responsibility. Perhaps ``unattached'' would have
been better, since when a reference is non-void the official terminology
is now that it is ``attached to'' an object, but it is hard to beat the
simplicity of ``void''.)

The above would seem to imply that I disagree with Mr. Cumming but in fact
I don't. I just want to make sure the semantics is the right one.
The example which really drove me to consider something along the
lines of what he is suggesting is
the general-purpose procedure ``print'' which is now in the universal
class ANY (from which every class normally inherits). For (almost) any
entity a, including integers and the like as well as entities of class
type, the execution of

    a.print

prints a suitable representation of a. (By default, for a class type, this
is just a concatenation of all field values; the procedure can also be
redefined to produce a different form.) Now there is nothing magical
about ``print'': it is a normal procedure of class ANY, and hence of any
other class. This means that if a is void the above will produce an
exception. Such behavior is not very nice in practice: a typical
application of ``print'' is to output extra information when you are
trying to chase a bug, and you will hate your system for triggering an
exception just because you are asking it to print auxiliary information
which, in a certain case, just happens not to exist.

This is indeed a circumstance in which the ``do nothing and keep silent''
response does seem the appropriate one.

By the way, it is in fact possible to achieve this behavior in the current
(2.2) system. By inheriting from the Kernel Library class EXCEPTIONS
and including a call

    continue (no_object)

you ensure that the exception of code ``no_object'' (arising from
attempting to apply a feature to a void reference) leads to continuation
of the execution. I would not advocate this as a general technique,
however.

What may be appropriate is the possibility to include EXPLICITLY in the
declaration of a certain routine (this does not seem to apply to attributes)
a mention to the effect that it is OK to use a void reference;
then the routine has a null effect and, if it is a function,
it returns the default value.

This would be specified in the declaration of the routine itself. I
have not started looking for a proper syntax seriously yet, but it might
be something like
 
    r (...): ... is

        default

        require
          ....
        do
                (the rest as usual)

Here ``default'' is just an extra qualifier.  I have also played with
the idea of a ``default'' clause which would specify a behavior possibly
different from the null effect, but it becomes tricky and does not seem
worth the trouble. In particular, the postcondition (see below) becomes
overly complicated.


If a routine has a default clause, the routine's contract does not
entitle the client to the postcondition, as with a normal
routine. Instead of POST, the guaranteed exit condition (for a call x.f (...),
where POST is the postcondition of f) is now

        (not x.Void) implies POST

(In pre-2.2 Eiffel this would have been written ``x.Void or else POST''). 

So this does seem to have a clean and easily teachable precise specification
in terms of assertions, which is one of the essential criteria for
judging this kind of language trait.


-- Bertrand Meyer
bertrand@eiffel.com