florman@randvax.UUCP (Bruce Florman) (04/25/89)
I recently came across a problem with multiple inheritance and
deferred routines which, while not insurmountable, can only be handled
by some ugly, inefficient, and possibly non-portable programming
practices.
I am writing a small discrete-event simulation, and one of the
most primitive classes is the LOCATABLE class, which is used to
represent physical objects whose location may be expressed as a
single point. One of the exported routines of LOCATABLE is a
deferred routine named `location' which returns an object of type
POINT. There are three heirs to LOCATABLE which provide alternative
implementations of the location routine. The first is the INDEPENDENT_
LOCATABLE class, which simply stores the location in an attribute named
`current_location'. The second is the DEPENDENT_LOCATABLE class, which
defines an attribute named `host' of type LOCATABLE, from which it gets
its location. This allows me to represent things which are discrete
objects, but which are attached to other locatable objects, generally
ones which can move. The last heir to provide an effective definition
of the location routine is the SEMI_DEPENDENT_LOCATABLE class, which
inherits from both INDEPENDENT_LOCATABLE and DEPENDENT_LOCATABLE. Its
definition returns its current_location attribute if its host is void,
and returns its host's location otherwise. This allows the represen-
tation of objects which are only sometimes attached to other objects.
Another class which inherits from LOCATABLE is the class called
TWO_WAY_RADIO, which is used to represent things which both transmit
and receive radio signals. A TWO_WAY_RADIO must be LOCATABLE so that
the simulation can determine such things as whether any two of them
are close enough together to receive each other's signals, but the
actual implementation of the location routine is of no concern to the
TWO_WAY_RADIO class. Since there exist large radio installations whose
location is pretty well fixed (INDEPENDENT_LOCATABLE), and radios which
are bolted into cars and boats and planes (DEPENDENT_LOCATABLE), and
portable radios which may be passed from one person to another (SEMI_
DEPENDENT_LOCATABLE), we don't want to tie TWO_WAY_RADIO directly to
any of those classes.
The problem arises when I try to define an effective class which
inherits from TWO_WAY_RADIO. When I try to define, for example,
SQUAD_CAR_RADIO -- the radio installed in a police car -- simply as
an heir to both TWO_WAY_RADIO and DEPENDENT_LOCATABLE, the compiler
complains about a name clash, despite the fact that the name coming
from one parent has no effective definition, and the name coming from
the other parent is simply a definition of the very same deferred
routine.
The work around for this problem is simple, but stupid. The class
is defined as shown below.
class SQUAD_CAR_RADIO
...
inherit
DEPENDENT_LOCATABLE;
TWO_WAY_RADIO rename location as radio_location
feature
...
radio_location: POINT is do Result := location end;
...
end -- class SQUAD_CAR_RADIO
It certainly seems pointless and inefficient that every time any of
the TWO_WAY_RADIO routines accesses the location, it will have to go
through this glue routine. It seems especially pointless that, given
the above definition, any routines which are effectively defined
by classes above TWO_WAY_RADIO in the inheritance graph will also
have to go through this glue routine whenever they access the
location. This is because, as I pointed out in another posting a
couple of weeks ago, when an entity's dynamic class has more than
one effective definition for a given routine, the definition from
the last parent in the inheritance clause takes precedence, regardless
of whether it has been renamed. However, this inefficiency can be
eliminated by simply reversing the order of DEPENDENT_LOCATABLE and
TWO_WAY_RADIO in the inheritance clause above. I still haven't
heard from ISE whether this should be considered an implementation
dependent feature of Eiffel (maybe when people get back from Paris).
In _Object-oriented_Software_Construction_, Dr Meyer emphasizes
the concept of deferred classes as partial implementations of abstract
data types. It would seem to be consistent with this idea that two
deferred classes which each implement half of the abstract type may
be combined in a straight forward manner to fully implement that data
type. I feel that a more perfect Eiffel compiler would recognize the
case where a name clash arises from one parent effectively defining
an inherited routine that another parent doesn't. It would accept
the inherited definition as the effective definition for both parents,
possibly issuing a warning similar to the one issued for repeated
inheritance.
Undoubtedly there will be cases where the above behavior is not
desirable, so probably there should be a method that allows the
programmer to prevent the compiler from merging the definitions.
Perhaps this would be done by explicitly resolving the clash through
renaming.
Aside from its exorbitant cost and the fact that it isn't
available for the Macintosh environment, I'm still a great fan of
Eiffel. However it does still have some weaknesses, and it would be
nice to see them addressed in future releases.
-- Bruce Florman
florman@rand.orgbertrand@eiffel.UUCP (Bertrand Meyer) (05/01/89)
In <1981@randvax.UUCP>, florman@randvax.UUCP (Bruce Florman)
accurately describes a not infrequent type of situation for which the
Eiffel 2.1 notation is too complicated. The problem arises in multiple
inheritance when the programmer wishes to implement without much ado
a deferred routine from one parent by a compatible effective routine
inherited from another.
The following is a compressed version of his description, abstracting
from his particular example. (An asterisk, applied to either a class
or a routine, means ``deferred''. A plus means ``effective'',
i.e. non-deferred.)
________ ________
| | | |
| A* | f* | B+ | f+
| | | |
-------- --------
^ ^
| |
| |
\ /
\ /
\ /
\ /
\ /
\ /
\ /
\ /
\ /
________
| |
| C+ | f+
| |
--------
Let class A have a deferred routine f and class B have an effective
routine, also called f, with compatible signature and specification.
[Actually, if the classes have been designed separately, there is
no reason why the routines should have the same name. I'll keep
this assumption for the time being for consistency with Mr. Florman's
example; it will be removed later.]
> [Mr. Florman tries] to define an effective class C which
> inherits from both A and B, where f from B provides the implementation
> for the deferred f inherited from A. But the compiler
> complains about a name clash, even though the name coming
> from one parent has no effective definition, and the name coming from
> the other parent is simply a definition of the very same deferred
> routine.
>
> The workaround for this problem is simple, but stupid. The class
> is defined as shown below.
>
> class C
> ...
> inherit
> A;
> B rename f as effective_f
> feature
> ...
> f (...) is do effective_f (...) end;
> ...
> end -- class C
>
> It certainly seems pointless and inefficient that every time any of
> the C routines accesses the location, it will have to go through this
> glue routine. [...] I still haven't
> heard from ISE whether this should be considered an implementation
> dependent feature of Eiffel (maybe when people get back from Paris).
[It is strongly recommended that readers refer to the original posting for
a more complete discussion.]
Well, ``people'' have returned from Paris (at least some of the people
have) and are slowly recovering from jet lag. The problem was indeed
discussed at the Eiffel User conference there and the 2.2 solution
described. (One user contribution which discussed the problem was
the paper by Philippe Drix of ESEO in Angers.) Note that the
``simple but stupid workaround'' described by Mr. Florman may be found in
several places in the 2.1 graphics library (which fortunately will soon
be history).
A solution is provided in Eiffel 2.2 by the ``join'' subclause of the
``inherit'' clause. This is applicable in multiple inheritance when the
programmer wishes to use an inherited effective routine as implementation
for one or more inherited deferred routines, without using the ``stupid
workaround'' necessary in previous releases.
The solution is the following:
- Make sure that, after renaming, the effective routine and all the
deferred routines it is meant to implement have the same name.
[In Mr. Florman's description, this was the case without renaming
since both routines had the same name, f.]
- This causes a name clash.
- The name clash is made legal by introducing a subclause of the form
``join f'' for all deferred routines that are to be implemented
by the effective routine. (In the above example and most practical
cases there is only one deferred f, but the mechanism is applicable
to any number.)
In the above example the solution becomes:
class C
...
inherit
A
join f;
B
feature
...
... no special declaration is necessary ...
...
end -- class C
Consider now the more general case in which the original
deferred and effective routines did not necessarily have the same name:
________ ________
| | | |
| A* | f* | B+ | g+
| | | |
-------- --------
^ ^
| |
| |
\ /
\ /
\ /
\ /
\ /
\ /
\ /
\ /
\ /
________
| |
| C+ | f+
| |
--------
Then the declaration of C should be:
class C
...
inherit
A
join f;
B
rename g as f
feature
...
... no special declaration is necessary ...
...
end -- class C
Of course, f could have be renamed rather than g, with an inheritance
clause of the form
inherit
A
rename f as g
join g;
B
feature
...
Syntactically, the ``join'' clause comes after ``rename'' and
``redefine'' if any. It is of the form
join f1, f2, ...
where f1, f2, ... are deferred routines in the corresponding parent class.
For each routine fi listed in this subclause, the class must contain
exactly one effective routine with the same name and a compatible
signature/specification. (Otherwise the class is erroneous.)
Then the meaning of the subclause is simple: ``Implement each fi in this
class by the corresponding effective routine.''
To take the ``join'' mechanism into account, the renaming rule
(see ``Object-Oriented Software Construction'', page 278, second box)
should be updated as follows:
|-----------------------------------------------------------------|
| Renaming rule: A name clash is said to occur in a class D when |
| two features inherited from respective parents B and C have the |
| same name f in D (taking into account any renaming done in D). |
| |
| Such a name clash is only permitted in the following two cases: |
| |
| 1. (Sharing in repeated inheritance.) If f was defined, etc. |
| [Text unchanged until ``(if it is a routine)''.] |
| |
| 2. (Joining deferred routines.) If the routine corresponding to |
| f in C is deferred, the routine corresponding to f in D is |
| effective, they have compatible specifications, and |
| the ``inherit C'' clause in B contains a ``join'' subclause |
| listing f. |
| |
| Any other name clash is illegal (and should be removed by |
| renaming). |
|-----------------------------------------------------------------|
A more complete description of the mechanism is contained in the
document ``Release 2.2 overview'', which will be posted later. Please
note that fine details of the join subclause (including the keyword
itself) are still subject to change.
-- Bertrand Meyer
bertrand@eiffel.com