[comp.lang.eiffel] Should a constructor do "work"?

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

alonzo@microsoft.UUCP (Alonzo GARIEPY) (07/12/90)

This doesn't seem like such a big problem.

If it is possible for an object to exist in a partially initialized state,
that should be taken into account by the class invariant.  Some kind of
flag value will suffice to identify the condition of the object.

After a successful allocation in a constructor, the first thing to do is
set this partial initialization flag (an operation guaranteed to succeed).
The constructor can then return, or it can attempt to fully initialize the
object.  If the initialization succeeds, the last operation in the constructor
is clearing the partial initialization flag (another guaranteed successful
operation).

If the actual memory allocation fails, the exception can be propagated
upwards until it results in a valid state.  One hopes there is no need to
return nil pointers as the result of failed construction, but special code
to handle nil does allow it to work relatively well.

alonzo@microsoft

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

rick@tetrauk.UUCP (Rick Jones) (07/16/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.
>
>	This should definitely be part of the language specification.

I agree, it SHOULD, but at the moment Eiffel doesn't do it that way!

If a rescue clause traps an exception which results from a Create, the
reference on which the Create was called is not Void.  This is true for
whatever reason the exception ocurred, including violating the Create's
preconditions .

Is this little inconsistency going to be corrected in future?

-- 
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/17/90)

From article <518@tetrauk.UUCP> by rick@tetrauk.UUCP (Rick Jones):

 
> If a rescue clause traps an exception which results from a Create, the
> reference on which the Create was called is not Void.  This is true for
> whatever reason the exception ocurred, including violating the Create's
> preconditions .
> 
> Is this little inconsistency going to be corrected in future?

Yes with respect to our implementation.

The language specification will specify this clearly for other
implementors as well.
-- 
-- Bertrand Meyer
bertrand@eiffel.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."

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.