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