Inheritance Considered Harmful
Dean Tribble
tribble@netcom.com
Sun, 25 Oct 1998 22:07:47 -0800
This'll be my E message for the week....
> Btw, I understand and mostly agree with all the abstract arguments about
> why inheritance is evil. However, as a programmer, in every language in
> which I've programmed in a SUIL style -- including Joule! -- I've found it
> pleasant. Joule actually supports something weaker than standard SUIL
> (inheritance of behavior but not state), and we were surprised to find
> ourselves wanting the missing piece -- despite the fact that Joule's
> support for delegation is as good as E's (because E stole these ideas from
> Joule).
>
> Dean was the one that ran into the rude surprise. Perhaps we can get him
> to comment????? (please please please)
There's what Joule v0.6 and previous did, and there's what we realized based on
that experience. Type hierarchy is a semantic mechanism that, while it can lead
to poor code with fat interfaces, can lead to clean abstractions with useful
taxonomies. Class inheritance is a code implementation sharing mechanism that
can lead to a variety of confusions in the type hierarchy as people twist their
interfaces to share more code.
Joule v0.6 tried to avoid or reduce this problem by allowing only default
behaviors defined in terms of other public methods of the class. This means
that "square()" method on Number can have a default implementation of "this *
this" essentially as an operational specification. This gave a measure of code
and implementation sharing that simplified the design of clean abstractions.
Ah, but when trying for something more complicated--general power Vector
constructs based on Ropes that interacted efficiently and smoothly with the
primitive array types--that mechanism for implementation sharing was
insufficient. It requires the ability to imclude state in the code sharing
mechanism. The simpler example we came up with after this was implementing a
caching hash that generates/computes a hash value once and then caches it for
quick reuse' lots of completely independent classes might want to use this
shared mechanism, but choice of this mechanism may be independent of where in
the type hierarchy the class is.
One more aside: there are several variations on "*-typed": by strongly typed I
assume declared types such as C++ and Java. Scheme is latently typed, in which
any variable can be any type, but the types are distinct, and any object is of a
specific type. Smalltalk is essentially untyped because any class can define
any method. Python and Perl are untyped. By design, Joule is latently typed,
though all the primitive abstractions must be prepared for untyped objects...but
that's another story. My understanding of the proposed inheritance mechanuism
is that E is latently typed (at least architecturally). I believe that latently
typed is important for building coherent abstractions, and E should take that as
its model (even if it cannot be forced in the implementation). Note that the
primary organizaing advantage that enforcing C++ disciplines on Smalltalk code
was the latent typing of objects in Smalltalk.
Where did we end up for Joule design? Types are in a (single inheritance)
hierarchy. Servers are latently type; i.e., only implement the methods defined
for their type. Servers have multiple facets. Types are pure interface (well
actually they define sealers...), and provide no support for implementation
sharing. Implementation sharing is provided through delegation (from the
delegator) to delegatees that implement a subset of the delegator's type (thus
you could just delegate the hash behavior). The delegatee could have state.
The delegator could parameterize the delegatee with access to its internal
state, so in implementation terms, the delegator could provide the slot into
which the delegatee would cache the hash. Note that specific methods or subsets
of the interface were delegated to the delegatee. The delegatee doesn't get to
add methods. I've bopped back and forth between definition and implementation
consequence, but this should be sufficient to remind you. Note that the
original E delegation has some substantial similarities.
I think that E avoids the most delicate part: define the semantics as patterns
of communicating, asynchronous objects that can be on separate machines with
forwarders in the middle, while still allowing the implementation magic to
implement message dispatch at almost C++ speeds....
> Since my preference is to err on the side of being too simple, until this
> issue is resolved, E's inheritance support sugar (the "class" construct) is
> hereby removed from the language spec. The documentation will show the
> inheritance-by-delegation pattern merely as an example delegation pattern.
I recommend against this based on my objections to the overloading of lambda in
scheme: if inheritance/delegation/class is an important pattern, support it
explicitly with syntax so that it is clearly a separate construct and concept to
learn. Eliminating the syntax but keeping the concept merely obfuscates the
issue.