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.org
bertrand@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