MOO-cows Mailing List Archive

[Prev][Next][Index][Thread]

Re: [MC] Multiple Inhertiance



On Wed, 10 Apr 1996, Matt Pauker wrote:

> Multiple inheritance is not an easy issue in the least, and is one of the
> more confusing aspects of languages (Such as C++) that do support it.  In
> my opinion, it leads to more problems than it solves.  If you have a
> situation where you need multiple inheritance, it could probably be
> rewritten in a better way to only require single inheritance.

It's very hard not to agree with this.  Multiple inheritance is powerful,
and in some ways it is intuitive (a square, for example, combines the
properties of a rectange and a rhombus).  However, implementing it tends
to be anything but simple -- it's almost always a mess of special cases
and complications.

I think that as people try to implement this, they'll run into many, many 
exceptions that nobody's thought of yet.  On the other hand, if a 
workable implementation was done, it would be pretty neat.

MOO is designed to be easy to program, and easy to learn to program.  To 
a programmer who's just been introduced to object-oriented programming, 
single-parent inheritance can be difficult enough (remember when you were 
first learning MOO programming?  Did you really need another thing to 
deal with?)  The real question here is whether the added power of 
multiple inheritance is worth the price of the added complexity.

That said, I have a "restriced multiple inheritance" scheme that might 
offer some of the benefits of multiple inheritance without a lot of the 
added complexity and headaches.

The essence of the scheme is this:  we allow multiple inhertiance only if 
a the inheriting object does not end up with conflicting definitions of 
verbs or properties.  That is, if an object currently has an ancestry chain 
that defines a certain property, another ancestor that defines the same 
property *cannot* be added -- same with verbs.

Common ancestors in multiple paths may be allowed, because they present no
conflict in inheritance definitions.  (Doesn't matter which tree you take
to get there, it's the same verb...) One path may define a verb on a
common ancestor as well, in which case that verb overrides the version on
the common ancestory.  (A side-effect of this is that an ancestor in 
multiple tress must be checked for a verb after all objects in any path 
there -- for example:


                                 |
                                 A
                                / \
                              /     \
                             B       C     D
                             |        \   /
                             |         \ /
                             E          F
                             |          |
                             |          |
                             G          H
                              \        /
                                \    /
                                  \/
                                  I

When looking for a verb on I, we must look for it on G, E, B, H, F, and 
C *before* we look for it on A.  Since only one path can define the verb, 
it doesn't matter whether we check G H E F B C or G E B H F C, so long as 
we check B after E after G, and C after F after H, and D after F (we can 
even check D after A if we want to).

Furthermore, if the verb is defined on B, E, or G, it may not be defined 
on H, F, C or D, but it may be defined on A or in A's ancestry.  If a 
verb is defined on D, it may not be defined on G, E, B, C, A, or in A's 
ancestry.

Now that I write this out I'm hesitant to allow multiple paths to an 
ancestor, since my goal is simplicity.  What do other people think?
[And now people should see why some people say that multiple inheritance 
is complicated -- my example isn't even that messy of a case...]

As a final feature, we may want to specify that a specific verb or 
property does not propogate.  For example, we've got an object that has 
some stuff that we want descendants to inherit, and then we've got a verb 
that prevents anyone from, for example, moving it, that we don't want to 
propogate because we expect it would conflict with another ancestry 
tree.

On the other hand, we might be able to avoid this with a well-designed 
common ancestor.

What does this buy us?

One thing I've thought of:

The current standard thing that avoids multiple inheritance but still 
allows adding arbitrary neat things to players is feature objects.  This 
scheme gives us the essential equivalent of those -- we can have fertile 
objects, with no ancestors or descended from the root class, that have the 
verbs on them that we want to add players.  But, we gain the following 
benefits:

1) The object can add properties to the player.  How many times have we 
wished for an easy way for an FO to keep information about each player?  
Either the FO need to keep the information on itself (with associated 
byte-quota difficulties, etc.) or the FO needs to add the property on the 
player, for which it needs permissions.  Messy.

2) It's faster.  Currently, selection of verbs on FOs is done by the 
MOO-code in :huh and related verbs.  With multiple inheritance it can be 
done with compiled C code in the server, which is *much* faster.

3) It simplifies permissions checks on those verbs.  Since they're verbs 
genuinely in the ancestry, we can set them -x if we want to, or put a 
standard permission check in them, and then the player can enhance them 
and call pass() just as with a normal verb now.

So, what do people think of this?  Is it still too complicated/messy?  
Have I missed something really big, or a slew of minor details that make 
it not worth it?  I just thought of it, so I'm not so sure that it's a 
good or workable idea, but it does seem to be worth some thought...


Now, returning to other scheme:

> 
> >*  When a verb is called (foo:bar), a list of objects is generated.  This list
> >   will roughly be the contents of a breadth-first traversal of the parentage
> >   tree, generated as follows:
> >
> >   First, add the object foo with.  Then add each of the objects in
> >   foo.parents with now, take each of their parents, and add them to the list.
> >   Continue until all have hit #-1.
> >
> >   This technique, however, produces repeats in the list.  Here's how they
> >   will be dealt with: during the ranked_parent list compilation stage, if a
> >   repeats is found, the first occurrence is removed from the list, and the
> >   object ends up at the end of the list.
> >
> >   In the end, a list of objects beginning with the object, and ending with #1
> >   will have been generated.
> >
> >*  Using this list, each object is scanned for "bar", until it is found.  If it
> >   is NOT found, a E_VERBNF is raised.  Partial verbs (bar*) will be matched at
> >   the same time, as they have the same priority as unambiguous ones.
> 

> What if you find more than one object with a matching verb?  You didn't
> handle this (common) instance.

That's what the .ranked_parent list was for.

The way we match verbs is analagous same that we currently do.  
Currently, we go through a list of objects, starting with the object, and 
then its parent, its grandparent, etc., until we get to #-1.  On each 
object, we look for the matching verb:  if called from the command line, 
with matching args, if called from another verb, with the x bit set, etc.

Now, instead of using the parent hierarchy, we use the .ranked_parents.  
We do exactly the same thing we do now, but with a different list of 
objects.  And, as we do now, we do the same thing for pass()ing, 
command-line matching, or straight verb-calls, just that we're looking 
for different properties in a verb.

> You haven't handled properties, which could be difficult to work with in a
> multiple inheritance situation.  If an object has multiple parents, which
> properties do you plan on using if there's overlap?  These kind of issues
> are difficult.

The hard part isn't whether to keep the property -- you use all of the
props defined on any ancestor; it's the permissions on it.  If two
ancestors define a !c prop, who owns it on the child? 


...next message...


On Wed, 10 Apr 1996, Seth I. Rich wrote:

> I notice that your design doesn't support multiple inheritance for command-
> line verbs.  Why not?

I don't understand why you think this is true.  We do the same thing for 
any verb call, command-line, passed(), called from built-in function, or 
called from verb -- but we're just looking for different things in the 
verb.  What's changed is the list of objects that we check for those 
things.  Maybe I'm reading the proposal differently than you...

> I think that once again this is real bad design; the MOO server isn't 
> designed for multiple inheritance and this will be SLOW SLOW SLOW SLOW 
> in production use.

Someone with more in-depth knowledge of how the server works may correct 
me here, but:

It seems to me that it doesn't really uniquely add overhead in the case 
of most verb calls.  We're doing the same thing we do now, but instead of 
working up the inheritance hierarchy, we're working down the 
.ranked_parents property -- all in nice fast C code.  What would make it 
slow is if the inheritance tree for an object became very complicated and 
the .ranked_parents property very lengthy, but the same thing happens now 
if an object has a long list of parents back to #-1.  (Admittedly, 
allowing multiple inheritance will tend to make the ancestry list longer, 
but many of those ancestors might replace FOs where we'd end up doing the 
same thing, but with slow MOO code instead of fast C code.)  What made 
the verbified-properties so slow was that it resulted in lots of extra 
invokations of the MOO virtual machine, which is relatively slow.

Of course, the scheme adds a *LOT* of overhead to anything that modifes 
the inheritance hierarchy.

  --Chris                           cunkel@engin.umich.edu
    ResComp Network Support Technician, Bursley Hall
    "Invisibility is in the eye of the beholder."
    Home Page: http://www-personal.engin.umich.edu/~cunkel/








References:

Home | Subject Index | Thread Index