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

Re: ECOOP Reaction to CLOS

Sorry it's taken so long to respond.

> The CLOS proposed standard explicitly claims in general to obey the
> implicit inheritance - explicit override rule, but it does not for

I couldn't find a reference to "implicit inheritance - explicit override"
in Part 1 of the 87-002 spec. What page is it on?

> In as much as implicit inheritance is a sub-conscious assumption of
> application programmers, standard CLOS behaves in a counter intuitive
> manner.     

In my opinion, inheritance should *never* be a sub-concious assumption.
It should be an explict design decision, made after considering 
alternatives. If the supers are in a library to which the programmer
does not have source, then enough information should be given in the
library's documentation that the programmer can design accordingly.
Full analysis might not be required during a prototyping phase, but
nevertheless, the programmer should be familiar with the details of
the supers.

> (defclass border (object) (width))
> (defmethod close (b border) ...)

> (defclass window (object) (width))
> (defmethod close (w window) ...)

> (defclass bordered-window (border window))
> (setq b-window (make-instance 'bordered-window))

> (close b-window)  ; with the current inheritance algorithm, only closes
> the border.  Does not close the window.

> The following explores what could happen if the implicit inheritance -
> explicit override rule were followed in CLOS.  

> (close b-window) would result in both the border and window close
> methods getting called because it inherits them implicitly and has not
> explicitly overridden them.  

> With implicit inheritance a class may have in addition to multiple
> methods with the same name that all get called by one call, multiple
> occurrences of a slot with the same name that are manipulated in one
> operation so b-window would contain two slots named width.

> (setf (slot-value b-window width) 0)  

> Would set both slots to 0.

Consider what would happen with a join superclass. In your example 
OBJECT is a join superclass, since it is inherited through two supers
(WINDOW and BORDER) in BORDERED-WINDOW. For the supers above the join, 
the base class gets two copies of slots, one through each branch through
the join class. There are two alternatives about what to do with the
two sets of slots:

1) Keep a seperate set for each branch of the join,
2) Merge them in some manner.

If the first approach is taken, then the object essentially has duplicate
copies of logically the same state. This approach was used in
CommonObjects, and is one aspect of CommonObjects which seems to bother
programmers the most. If the second approach is taken, it is possible
for some methods to be invoked more than once on the same set of
slots, making the method semantics for singly inherited and multiply 
inherited cases different (see below for more discussion).

> A problem is what to do with the results of an operation on a slot name
> that refers to two or more slots or on a call that refers to two or more
> methods.  Operating on these slots or methods that have not been
> overridden could be specified to return a "multiple-inheritance-result"
> object containing the multiple results.  

This seems to me to be more complicated than most application programmers
will want. It would be hard to integrate with WITH-SLOTS, and would require
more syntatic machinery for setting or getting slots, pathnames to the
appropriate slot, or something similar. It seems inappropriate to
burden programmers with having to deal with such extra syntatic 
machinery when slot merging will do the job most of the time.

> Any code that depended on the results from instances of singly inherited
> classes would not work correctly with instances of multiply inherited
> classes. (In the example above, any code that depended on the value

This is a general problem with linearizing multiple inheritance, 
but most programmers who have used Flavors or CommonLoops are used to it,
and plan accordingly. It requires more knowledge about the supers 
than for singly inherited languages, however.

The problem here is that every algorithm for multiple inheritance loses
somehow. If linearizing multiple inheritance is used, then the problem
with changing method semantics occurs. If tree-structured multiple
inheritance is used (where the entire tree of supers
is maintained, duplicating branches above joins) then duplicated state
occurs above join classes. If graph structured multiple inheritance is
used (where the graph of supers is maintained without duplication), 
methods can be invoked multiple times on the same slot.
For a more detailed analysis, see Alan Snyder's paper in the last 
OOPSLA proceedings.

What CLOS has tried to do (I think) is to provide Lisp programmers with
what they are most used to (i.e. codify existing practice), and to
do so in a manner which produces expected behavior most of the time.
If a programmer requires something out of the ordinary, then more time
and effort needs to be invested in learning more details, and, with
the metaclass protocol, much flexibility for changing inheritance
behavior is available. Where the CLOS inheritance algorithm could be
faulted, is that it handles the inability to linearize an inheritance
graph as an error, rather than providing hooks for the programmer
to override the default.

Alternatives to multiple inheritance for achieving the same effect
(i.e. maximum code reuse) are the subject for another basenote and
mailing list.

		Jim Kempf		kempf@hplabs.hp.com