[E-Lang] Re: inheritance, delegation, mental models for beginners (was: down with `define' (was: newbie syntax: picayune points from a prejudiced programmer))

Mark S. Miller markm@caplet.com
Fri, 02 Mar 2001 15:55:41 -0800


At 11:01 AM Friday 3/2/01, Marc Stiegler wrote:
>[With inheritance] if the subclass overrides a
>method in the superclass, when the superclass calls that method it goes back
>out to the subclass's method. With delegation, a delegated-to class call to
>the overridden method is directed to the delegated-to class implementation.

As Dean's message reveals, I may be badly out of touch with how the world 
uses terms these days.  However, my use of these terms agrees with MarcS's, 
which I believe agrees with the website and the list.  Therefore, when you say 

> The MUD programming languages [...] use the word
> "inheritance" to mean the thing that we call "delegation".

followed by

> Since E is dynamically typed there is no really important difference, is 
> there? [2]

since the second doesn't correspond to our usage, I remain curious about 
what MUD programmers actually do mean by "inheritance".

Regarding academia's use of these terms, perhaps the definitive document is 
the Treaty of Orlando
http://lieber.www.media.mit.edu/people/lieber/Lieberary/OOP/Treaty/Treaty.ps


>In fact, I have thought about designs for a few programs for which
>inheritance would make the system easier to understand and maintain, [...]

I've got one for you: the shamefully undocumented CopyVisitor from within 
the E language implementation 
http://www.erights.org/javadoc/org/erights/e/elang/visitors/CopyVisitor.html .

Given a tree of typed nodes, such as Kernel-E parse trees, we know that 
we'll want to write several algorithms for walking such trees and returning 
similar trees derived from these trees by some transformation.  The current 
E language implementation has three such tree transformers: RenameVisitor, 
AlphaRenameVisitor, and SubstVisitor.  For all of these, for most of the 
types of parse nodes, they simply want to copy that parse node while 
*filling in the likewise transformed version* of the children of that parse 
node.

The copying behavior they have in common is implemented by the CopyVisitor.  
The CopyVisitor is carefully written so that it can be reused in an 
equivalent fashion whether by Java-style class-based-inheritance, or by 
E-style inheritance-by-static-delegation 
http://www.erights.org/elang/blocks/inheritance.html .  

The familiar class-based description first:

The actual RenameVisitor is implemented in Java as a subclass of 
CopyVisitor.  It overrides those methods it needs to change, but not the 
others.  For these others, CopyVisitor's copying behavior is used, which 
first *calls itself* to make a copy of the children of the node being 
copied, and then makes a copy of the current node using these copied 
children.  Of course, the "itself" above is actually a RenameVisitor rather 
than a pure CopyVisitor, so these children contain transformed copies of the 
original children.

The equivalent instance-based delegation description:

RenameVisitor could have been implemented in E as follows:

    def newRenameVisitor(renamings) :any {
        def self := {
            def super := newCopyVisitor(self)

            def RenameVisitor {
                to visitCatchExpr(...) :any { ... }
                //... likewise for all parse node types 
                //    needing transformation...

                delegate { super }
            }
        }
    }

CopyVisitor could have been implemented in E as:

    def newCopyVisitor(self) :any {
        def CopyVisitor {
            to visitCallExpr(recip, verb, args) :any {
                def transformedRecip := recip welcome(self)
                //... transform args ...
                newCallExpr(transformedRecip, verb, transformedArgs)
            }
            //... other parse node types ...
        }
    }

This can be seen as a combination of visitor and decoration, or we can just 
name this pattern "inheritance", since it exactly produces (with 
instance-based delegation) the semantics associated with class-based 
inheritance.

Given that we keep "delegate", notice how the "class" construct provides us 
no additional help with the above code.  Why?  Because RenameVisitor is (in 
Java terminology) concrete and final, while CopyVisitor is essentially 
abstract.  The class construct only helps in defining the equivalent of 
classes that are concrete and non-final.  The price of leaving out the 
"class" construct is only that it becomes four lines more difficult to write 
one object-maker that supports both direct instantiation and inheriting (be 
delegation) from the objects it creates.

Btw, many of us, when doing class-based programming with inheritance, mostly 
stopped using concrete non-final classes since the Udanax Gold days.  When 
we found we wanted to subclass a concrete class, we refactored so that we 
were subclassing from an abstract class.  I believe Dean was always the 
biggest advocate of this design rule.  The E implementation grossly violates 
this, but that's only because of Java used to have a horrendous huge 
per-class memory cost.  (It may still, but we should fix this anyway, since 
memory is now cheaper.)

So my current inclination is to leave out the "class" construct, go with 
MarcS's definition of "inheritance", rather than Zooko's  and to truthfully 
observe that E supports inheritance perfectly well.  And then expand on this 
in the Walnut, including the qualifier about concrete non-final classes.


On a different and depressing note, in writing the above code, I noticed a 
reason not to use the "newThing" convention:  I first wrote

                def newRecip := recip welcome(self)

and then I caught myself.  I think this mistake will always be natural.  
Better not to have a convention that conflicts with the above line.  But I 
really dislike "classThing".  I like "ThingClass" only slightly better.  Are you 
sure you don't like the current "ThingMaker"?  Instead of either pretending 
it's all just classes, or throwing them into the deep end of lambda 
concepts, can't we introduce Makers by saying:

>Makers in E are like classes.  You define one as follows. [all Makers have 
>a "Maker" suffix on their name.]  You use one as follows:
>
>    def foo := ThingMaker(...)


Only later need we reveal that the ThingMaker pattern is only a pattern, not 
class-like magic.



        Cheers,
        --MarkM