Subject: Re> Multiple Inheritance

From: [email-masked] (Jeff Prothero)
Date: Mon, 29 Mar 1993 09:51:49 -0800

[email-masked] writes:

| I hate it when I forget to Cc the mailing list.  Can you forward it
| for me?  Thanks.

*grin*

------------------- begin forward ---------------------

Jeff brings up some valid concerns, which I've considered in the past,
but I still think my scheme is better in practice.

Referring to the example:

         Root object
           /     \
         a         b
           \     /
              c

> We could, for example, make an arguably equally plausible appeal to
> intuition on the grounds that the verb definition in 'Root' is
> "presumably" (*grin*) more general than that in 'b', which is likely
> specialized to 'b's peculiar properties, and that 'c', by inheriting
> a much more general environment by mixing 'a' and 'b', might well in
> fact be much better off with a general verb definition than one
> perhaps too specialized to function in 'c'.

> when choosing to inherit behavior from a class, we would prefer not
> to have to refer to the internal implementation detail of that class
> in order to understand the resulting behavior of the system.

This is true, and in fact it made me hesitate before implementing the
system I described.  However, I think that in practice the
functionality of many objects is precisely in their overriding of
certain behavior on the parent object.  For instance, I've seen
objects implemented in MOOs like "Generic transparent exit".  Its sole
functionality is to override the :description() verb on its parent.
If I want to inherit from this object in a depth-first-search system,
it would clearly have to be the first parent of my child object.  But
this means I cannot mix two classes whose functionality is to override
different verbs.

However, you still don't have the same idea of encapsulation
conceptually, since you do have to consider the identity of the
parents of an object in determining the behavior of an object.  Let's
take a look at the harm Jeff believes this would cause:

> For example, a maintainer can no longer assume that it it safe to
> insert a new superclass in a given hierarchy (in order perhaps to
> share some code with a newly created class -- a common sort of
> maintainance programming in an oo system) merely if all pre-existing
> classes export the same behavior after the change: the maintainer
> must verify that the change in treedepth of each moved method does
> not break the behavior of any _user_ of any of the classes by
> implicitly switching it to another method.

I would find this more plausible if I could see a concrete example.
There is no difference in my scheme between the digraphs

         Root object
           /     \
         a         b
           \     /
              c

and

         Root object
           /     \
         a
	 |	  b
	 d
           \     /
              c

assuming 'd' and 'a' as a unit act in the same way as 'a' did
previously.

Likewise, the digraph

         Root object
       /     /  \    \
     d      /    \     e
       \   /      \  /
         a         b
           \     /
              c

won't "break" anything on c, except that verbs definitions on d will
apply before those on b, as they would in a depth-first search.
(Verbs on e will apply before verbs on the root object, which may be
what you're referring to, although then b is exporting different
behavior, so I don't see how it applies.)

To be fair, there is one sticky point.  If I reimplement a root object
verb on d, which has exactly the same behavior as the root object
method, then a has the same behavior as it did before, while c changes
behavior.  While this is a "smoking gun" example of loss of
encapsulation, it's not clear to me that this would cause a problem in
practice, since I don't see *why* you'd want to reimpliment a root
object verb on d.

There is another reason why I favor my scheme over a
depth-first-search, although it may not be very relevant to the way
things are done in MOOs currently.  I can conceivably see that a mud
maintainer might want to implement default behavior on ancestors
shared by a lot of objects--for instance, an "open" verb on "Generic
thing" that displays an error message, "You can't open that."  This
doesn't seem like a valid strategy in a database designed like
Lambdacore, since a player would then be able to examine the object
and see an 'open' verb on it that doesn't work.

One more issue, on efficiency:

> (Changing updating the parent pointers in an object from an O(1)
> operation to, worstcase, an O(N) operation in the size of the db, as
> the coldmud approach appears to do, seems also unappealing,
> especially in a diskbased system.)

You have to traverse the children digraph of the object, yes.  This is
unpleasant, but I think it's fairly rare to change the parent pointers
on, say, generic room, while the database is large.

--GBH