bertrand@eiffel.UUCP (Bertrand Meyer) (07/07/90)
A class is not just a bunch of operations packaged together. It describes a data type, whose instances must satisfy some consistency constraints, called the ``class invariant''; in Eiffel the invariant is part of the class text. The class invariant usually contains two kinds of clause: some clauses characterize the underlying abstract data type and are common to all its implementations; others only apply to an implementation, or a group of implementations, and are called ``representation invariants''. This has been discussed in detail in various publications. The worst situation that can ever happen in the execution of an object-oriented program - the ``mortal sin'' of object-oriented programming - is to produce an object that does not satisfy the invariant of its class. This annihilates any hope of reasoning about the software system, or of understanding what will happen during its execution. (One guaranteed way of committing the mortal sin is called static binding, but that is another story.) The purpose of having creation operations (constructors) in a class is to make sure that every object will satisfy its class's invariant at creation time. This is a formal requirement in Eiffel (Create must ensure not only its postcondition, if any, but also the invariant). Allocating an object without doing the work needed to ensure the invariant would be a trivial example of the ``mortal sin''. -- Bertrand Meyer bertrand@eiffel.com
rick@tetrauk.UUCP (Rick Jones) (07/09/90)
In article <359@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes: > > ... > > The purpose of having creation operations (constructors) in a class >is to make sure that every object will satisfy its class's invariant at >creation time. This is a formal requirement in Eiffel (Create must >ensure not only its postcondition, if any, but also the invariant). > A question I would like to ask (of anyone, Bertrand especially) is how should the language or the program design cope with a situation where an object is created, but its create routine ("constructor") is unable to satisfy the invariant due to some run-time condition which the program must trap in a non-fatal way. In Eiffel an exception could be raised, and caught somewhere else, but that would not in itself prevent the existence of the invalid object. The underlying question is whether a constructor should be allowed, under exception conditions, not to create any object and leave the corresponding reference void. -- Rick Jones You gotta stand for something Tetra Ltd. Maidenhead, Berks Or you'll fall for anything rick@tetrauk.uucp (...!ukc!tetrauk.uucp!rick) - John Cougar Mellencamp
bertrand@eiffel.UUCP (Bertrand Meyer) (07/13/90)
From <519@tetrauk.UUCP> by rick@tetrauk.UUCP (Rick Jones): > How should > the language or the program design cope with a situation where an object is created, but > its create routine ("constructor") is unable to satisfy the invariant due to some > run-time condition which the program must trap in a non-fatal way. In Eiffel an > exception could be raised, and caught somewhere else, but that would not in itself > prevent the existence of the invalid object. If invariants are checked and a creation operation raises an exception, the target of the Create should remain void, excluding any creation of an inconsistent object. This should definitely be part of the language specification. -- -- Bertrand Meyer bertrand@eiffel.com
sjs@roland.ctt.bellcore.com (Stan Switzer) (07/13/90)
The original question is whether it is reasonable for a constructor to do a great deal of work. People have answered (correctly) that it must at least do enough work to guarantee that its class invariant is satisfied. Sometimes, though, that can be too expensive and a "lazy evaluation" approach is in order. Suppose that the original class invariant is "C". Let the new invariant be "!initialized || C." All the constructor has to do is set "initialized" to "false" and squirrel away any initialization parameters. The price of this is, of course, an extra instance variable (or so) and that every method has to begin with "if ( !initialized ) initialize();" It's not appropriate for every situation, but it can be a quite useful technique when used wisely. This is actually a special case of the polar/cartesian representation problem discussed under "Legitimate side-effects: and example" in Meyer's *Object-oriented Software Construction*; in this case one of our representations is just the raw initialization parameters. Stan Switzer sjs@bellcore.com
wex@dali.pws.bull.com (Buckaroo Banzai) (07/18/90)
In article <364@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes:
If invariants are checked and a creation operation raises
an exception, the target of the Create should remain void,
excluding any creation of an inconsistent object.
I don't understand why this is so. I agree that it's one way to specify a
language, but surely it's not the only way.
For example, let's say that I create an object, and as part of that
creation, I wish to initialize some of its slots (oops, my CLOS is showing
:-).
Suppose then that some of the initialization fails. Can't I write a
specification that says "the value will be a list of numbers or NULL"? Then
when the initialization fails, I can return an object with that particular
slot having a NULL value.
This would fulfill the specification; are you saying that this would not
"raise an exception" in the sense that you mean it above?
--
--Alan Wexelblat
Bull Worldwide Information Systems internet: wex@pws.bull.com
phone: (508) 294-7485 (new #) Usenet: spdcc.com!know!wex
"The aim of life, its only aim, is to be free. Free of what? Free to
do what? Only to be free, that is all."
rick@tetrauk.UUCP (Rick Jones) (07/19/90)
In article <WEX.90Jul18120747@dali.pws.bull.com> wex@dali.pws.bull.com (Buckaroo Banzai) writes: >In article <364@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes: >> If invariants are checked and a creation operation raises >> an exception, the target of the Create should remain void, >> excluding any creation of an inconsistent object. > > ... > >Suppose then that some of the initialization fails. Can't I write a >specification that says "the value will be a list of numbers or NULL"? Then >when the initialization fails, I can return an object with that particular >slot having a NULL value. > >This would fulfill the specification; are you saying that this would not >"raise an exception" in the sense that you mean it above? This all comes down to being clear about what is and is not a valid state for the object, i.e. what the invariant condition is. You have effectively defined your invariant in the statement above by saying that a null list is a valid state. This is quite OK provided that the routines of the class are written to take account of the possibility of a null list, and so creating such an object would not raise an exception. The other approach is to say that your class routines are NOT going to tolerate a null list, and that you don't allow any such object to exist. This then raises the above problem in that a failure to create the list when creating the containing object is BY DEFINITION an exception condition. Apart from the general issue of exception handling, the special case of an exception during object creation should result in no object being created, and any associated reference remaining void. The choice of how to handle the situation is one of design, and one or other may be appropriate for different problems. However, I think the ability to clearly define and correctly handle exception conditions is a very valuable one, and if properly used can considerably simplify programs. There is some statistic about how much of a typical program is devoted to handling error conditions; I forget what it is but it's a large number. This seems to result from all the code which appears as in-line logic to handle what in reality are exceptions to normal execution. What is and is not an "error" gets very confused. I am using Eiffel, and planning to make maximum use of it's exception handling to deal with everything which is not a normal down-the-middle expected situation (at least at the prototype stage). This includes things which are not normally treated as errors, such as "record not found" on file access, or "wrong entry" at a terminal. This could be a good subject for discussion; has anyone any experience of taking this approach to building software? -- Rick Jones You gotta stand for something Tetra Ltd. Maidenhead, Berks Or you'll fall for anything rick@tetrauk.uucp (...!ukc!tetrauk.uucp!rick) - John Cougar Mellencamp
kim@spock (Kim Letkeman) (07/19/90)
In article <WEX.90Jul18120747@dali.pws.bull.com>, wex@dali.pws.bull.com (Buckaroo Banzai) writes: | In article <364@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes: | If invariants are checked and a creation operation raises | an exception, the target of the Create should remain void, | excluding any creation of an inconsistent object. | | I don't understand why this is so. I agree that it's one way to specify a | language, but surely it's not the only way. It is one of the best ways to avoid introducing a significant increase in complexity - that being the change from having two states for an object reference (good or bad) to having three (good, bad, we're not sure.) I've seen a lot of code that attempted to compensate for incomplete initialization and other subtle failures. Code that treats the world in a more binary fashion has (in my experience) always been simpler, faster and just as useful. | For example, let's say that I create an object, and as part of that | creation, I wish to initialize some of its slots (oops, my CLOS is showing | :-). | | Suppose then that some of the initialization fails. Can't I write a | specification that says "the value will be a list of numbers or NULL"? Then | when the initialization fails, I can return an object with that particular | slot having a NULL value. | | This would fulfill the specification; are you saying that this would not | "raise an exception" in the sense that you mean it above? You're really just saying that your class invariant would allow for some of the "slots" to be NULL. I.e. a failure during initialization of some of the slots would not actually violate the invariant. If your object is actually useful with these slots NULL, then you are being completely consistent with Meyer's statement above. If, on the other hand, your object is not useful (requiring a lot of extra code to handle special cases where NULL slots exist), then you have violated Meyer's statement and created an incorrect class invariant. I believe that an object should function exactly as the interfaces (methods, features ...) intend from the very moment of creation, or not exist at all. -- Kim Letkeman mitel!spock!kim@uunet.uu.net
bertrand@eiffel.UUCP (Bertrand Meyer) (07/19/90)
In <WEX.90Jul18120747@dali.pws.bull.com>, wex@dali.pws.bull.com (Alan Wexelblat?) questions my previous suggestion that >> If invariants are checked and a creation operation raises >> an exception, the target of the Create should remain void, >> excluding any creation of an inconsistent object. by stating > (...) Suppose then that some of the initialization fails. Can't I write a > specification that says "the value will be a list of numbers or NULL"? Then > when the initialization fails, I can return an object with that particular > slot having a NULL value. > > This would fulfill the specification; are you saying that this would not > "raise an exception" in the sense that you mean it above? But this is precisely the point. The invariant is here to state the specification. If ``null'' is an acceptable value, then the invariant should say so and there is no reason to pass an exception to the client. Otherwise the invariant is unsatisfiable and there should be an exception. To make this concrete, take the example of an Eiffel class with two attributes: n: INTEGER; l: LIST [STOCK] and assume we expect that the creation will initialize `n', perhaps from an argument to the creation operation, and allocate list `l' with a number of elements equal to `n'. Furthermore, the length of the list should remain equal to `n' during the lifetime of the object. Then the invariant clause for the class will say invariant l.count = n (`count' is the standard name used in the Eiffel libraries for the number of meaningful elements in a data structure.) In this case, if the creation operation fails to ensure the invariant (for example because there is no memory left to allocate `n' list elements for the requested `n'), then it would be improper and dangerous to create an inconsistent instance of the class. If, however, the class designer is prepared to deal with such objects, he may easily adapt the invariant clause to read (for example): invariant l.empty or else l.count = n In which case there is no ``failure'' visible to the outside world. No inconsistent object has been created. Assume for example that -- Bertrand Meyer bertrand@eiffel.com
diamond@tkou02.enet.dec.com (diamond@tkovoa) (07/23/90)
In article <372@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes: [moderately long, perfectly correct suggestion about invariants deleted] >Assume for example that > >-- Bertrand Meyer Oh no, then what would happen to Eiffel? :-) -- Norman Diamond, Nihon DEC diamond@tkou02.enet.dec.com This is me speaking. If you want to hear the company speak, you need DECtalk.