From: Dirk van Deun on
I have been thinking for a while now that there ought to be something
in between subclasses on the one hand, and mixins on the other.
I'll explain what I mean. Subclasses apply to one class only, but
they have the nice property that they access the members of their
superclasses through lexical scoping. Mixins on the other hand can be
applied to several classes, but they do not have the members of their
superclass in scope, so that references to members of superclasses
only get a meaning at the moment when the mixin is composed with a
class: mixin and class are then stitched together based on string
comparison of identifiers. This difference becomes important when the
class in question is itself composed of a class and a previously
applied mixin: through its "flattened" view, without real scoping, a
second mixin cannot know or specify whether an identifier will refer
to something in the original class, or something in a first mixin, so
that the order of mixins determines the meaning of the whole in an ad
hoc way. My goal then would be a language with components that are
more reusable than subclasses, but with real lexical scoping: or
something in between subclasses and mixins.

After having prototyped a language that has something in between, I
thought it a smart idea to stop playing with toy examples for a while
and seek the opinion of some Real Programmers: does my system actually
solve any real problems, or just some favourite academic examples ?
To solicit your opinion, and explain what the system is, I have to
quote the first three sections of the description of a toy language
that has no subclasses at all. This is only a bare minimum to get my
point across, but I do not want to scare away *all* potential
responders. :-)


Let us define a simple class Human as follows:

class Human {
method play_tetris() { ... }
}

Now in languages with subclassing, to derive a subclass Woman, we
would, using some specialized syntax like "extends", reopen the scope
of Human, and embed the class Woman inside it, so that the methods
and data of the class Human would be visible for any newly added
methods for Woman. Abusing Tingle syntax, as the language does not
allow of subclasses, we would write something like:

class Human {
class Woman {
method buy_more_shoes() { ... }
method give_birth() { ... }
}
}

The two new methods are able to use the Human method play_tetris(),
which is in an enclosing scope (for instance to while away the
time until a shoe salesperson is available).

In our language without subclassing, the above code is syntactically
valid; but it actually means that the classes Human and Woman overlap,
and that the two new methods exist in their intersection, available
only to objects that are both Human and Woman.

As all women are by definition also human, the class Woman and the
intersection coincide. Maybe we can make the example more interesting
by making it about the intersection of Human and Female instead:

class Human {
class Female {
method buy_more_shoes() { ... }
method give_birth() { ... }
}
}

class Woman = Human Female

We now have two independent classes, Human and Female, neither of
which is a super- or a subclass of the other; and thus we see that we
need a separate composite class Woman, for objects that are both
Human and Female.

As order is irrelevant for set intersection, we can change the order
of nesting:

class Female {
class Human {
method buy_more_shoes() { ... }
method give_birth() { ... }
}
}

Now we can express something that we could not express earlier: the
fact that give_birth() is typically female, but buy_more_shoes() only
applies to human females:

class Female {
method give_birth() { ... }
class Human {
method buy_more_shoes() { ... }
}
}

As might be expected, a more specialized give_birth() for human
females can be added in the intersection. If we would create an
object of the class Female without composing it with Human, it would
provide the basic method give_birth(), and no buy_more_shoes().

If we do create an object of the class Woman, on the other hand, all
most specialized methods will be available. Methods in the
intersection of Female and Human will predominate over methods with
the same name in the composing classes. For a Woman named mary,
mary.method() will refer to a method in the intersection if a method
with the right name is available there; if not, it will refer to a
method in either Human or Female. As there is no hierarchic
relationship between Human and Female, when both the classes Human and
Female define a method and the intersection does not resolve the
conflict, neither version of the method is predominant, and it is an
error to try to use it.

For a composite class that combines more than two classes in which a
same method name occurs several times, the predominant method, if it
exists, is the unique method that occurs in the intersection where all
the classes and all the intersections that define that name overlap.
Or to put it differently: specialization is a partial order among
methods; if there is one version that specializes all the others, that
one is the predominant method. If not, there is none.

Now let us reuse the class Female:

class Bovine {
method look_at_passing_trains() { ... }
}

class Cow = Female Bovine

For a Cow, only the part of Female outside of the intersection with
Human is relevant: the intersection with Human is simply ignored
in composing this new class. The intersection of Bovine and Female on
the other hand is still empty, so let us now add a method moo():

class Bovine {
method look_at_passing_trains() { ... }
class Female {
method moo() { ... }
}
}

The method moo() characterizes Bovine rather than Female -- I
personally associate the concept of mooing with bovines rather than
with femininity -- and that is why I add moo() to the definition
of Bovine, further nested in Female, rather than the other way around.
In section II we will see that the order of nesting, which is
irrelevant for determining the predominant method, is however relevant
for scoping, so in quite some cases the order of nesting will
probably rather be chosen with scoping in mind.

The class Female was reopened for this example: classes can be
reopened as often as needed, as an outer nesting as well as nested in
others. On the other hand, I did add the new code to the Bovine-block
I wrote earlier, instead of reopening Bovine, but only because the
two methods seem to go well together thematically.

When you arrange methods thematically by reopening classes several
times, tools can easily be provided, for instance as editor plugins,
to view the code by complete classes, or to see all variables in a
certain scope together, when this helps understanding. Tools for the
opposite direction would be much harder to conceive.


II Scoping

The order of nesting of classes is irrelevant to the identity of
predominant methods; but it remains the foundation of scoping, and so
determines the internal structure of composite classes.

Methods in an intersection can access only variables from classes that
make up the intersection. There are no free variables that get caught
at composition time: the order of nesting around a method definition
determines locally which variable an identifier refers to in that
method. The same goes for method lookup, when methods are called from
the active object -- that is, as a function call, as in "method()",
not by sending a message, as in "mary.method()".

class Female {
data children
}

class Bovine {
class Female {
method moo() { if (children > 3) ... }
}
}

In the body of moo(), the inner scope is formed by the intersection of
Female and Bovine; the next scope by Female only; and the next by
Bovine only. The variable children in the class Female is not in the
same definition, but it is in scope. (Here code browser plugins
for the editor as mentioned before would be useful.) If the variable
children had not been declared in Female, but in Human, moo() would
not work, not even for the godess Isis:

class Isis = Female Bovine Human

The method moo() does not see the variable children in the class
Human, although it is present in the composite class Isis.

Using the notation class::variable the programmer can explicitly
override the scoping order, but not refer to a class outside of the
scope. In moo() the distinction between Female::children and
Bovine::children could be made, but trying to use Human::children
would be an error, as Human is not in scope. To explicitly refer to a
variable or method in an intersection, use constructions like
Female::Bovine::moo(). (Order is unimportant.)

In our moo(), super.moo() will refer to Female::moo() if that exists;
if not, Bovine::moo() is tried. Super calls are function calls,
not messages.

For methods defined only two levels deep, I think everyone except the
most quarrelsome will accept this scoping rule as the obvious and
"correct" one. The complete rule as currently used in Tingle however
is only one of the possible generalizations of the more obvious one
for two levels. It is rather complex: it was chosen because it
assures that intersections always have precedence over their separate
classes, and also that inner scopes have precedence over outer ones.

class A {
class B {
...
class Y {
class Z {
method myMethod() { print(v) }
...
}

The variable v will first be looked for in the intersection of all
classes A through Z (where it would reside if it was declared
immediately above myMethod in the source code), then in the smaller
intersections B through Z, C through Z etc. When the intersection Z
through Z is reached, i.e. the class Z alone, and nothing has been
found, then we continue with the intersections A through Y until only
Y, then the intersections A through X until only X etc.

This way all continuous intersections (like C+D+E and P+Q+R+S+T) will
be searched; intersections are always searched before the single
classes that make them up, and also before intersections of only some
of the same classes; and inner classes before outer ones.

If you want to use a variable or method that is in a non-continuous
intersection, you can use the ::-notation; or change the order of
nesting for the current method. The latter solution is possible
because, as classes can be reopened as often as desired, each method
can have its own nesting order; then again, nested blocks provide
visible structure to the program, so they should not be chopped up too
much. Some taste might be required. This might also be the right
moment to remark that when you have to think hard about scoping while
reading a program, the program is probably badly written and might
benefit from more diverse and more descriptive identifiers.


III Programming Style

If the previous section leaves you with the impression that scoping
for methods many levels deep will be awfully complex, this section is
all about avoiding deep nestings. That does help in understanding
programs; however the real reason to avoid nesting wherever possible
is to promote reusability. (Every nesting is a dependency.)

We can emulate boring old single inheritance in Tingle:

class car {
method start() { ... }
method stop() { ... }
}

class bus {
class car {
method bus_stop() { ... stop() ... start() ... }
}
// nothing here -> classical single inheritance
}

class Car = car
class Bus = bus car

(There is no such concept as bus-ness that can be combined with the
concept "car" to make a bus out of it, so I use "bus" and "Bus"
for respectively the base class and the composite class. And "car"
versus "Car" is only for symmetry.)

This is all very well, but emulating multiple inheritance next would
lead to overly deep nesting: note that we only nest with base classes,
not with composite classes. (Composite classes have no internal
order; and if they would have one, that one would probably not be the
appropriate one in all circumstances.) A "literal translation" of
code with multiple inheritance would therefore be ugly, and its
scoping complex:

class carGUIObject {
class car {
method draw() { ... }
}
}

class busGUIObject {
class bus {
class carGUIObject {
class car {
method draw() { ... super.draw() ... }
}
}
}
}

class CarGUIObject = car carGUIObject
class BusGUIObject = bus car busGUIObject carGUIObject

There is a better way, with shallow scoping, and all draw() methods
snugly together in the source code:

class GUIdraw {
class car {
method draw() { ... }
class bus {
method draw() { ... super.draw() ... }
}
}
}

class CarGUIObject = GUIdraw Car
class BusGUIObject = GUIdraw Bus

This shows that there is no dominant decomposition by the sort of the
vehicle, and also that the "diamond problem" does not exist here.

Note that, if we want to add another concern later, we can write this
up without taking GUIdraw in scope, when the two concerns are
independent of each other:

class Accounting {
class car {
method costs() { ... }
class bus {
method costs() { ... }
}
}
class employee {
...
}
}

Again, scoping is shallow. We can compose all this with:

class CompanyCar = Accounting GUIdraw Car
class CompanyBus = Accounting GUIdraw Bus

All these good things come with a trade-off: in a system based on
intersecting classes, emulating "subclassing for construction" or
"subclassing for combination" is the way madness lies. The
relationship between a composite class and its parts should always be
"is a" or "is" or something similar: never "has a" or "is a bit like
a". (Use an instance variable to express "has a" relationships.)

-------------

These are the basic principles and some examples of how a language
built on them can be used. Ordinary subclasses could be added
as syntactic sugar, for instance. There is more at
http://dirk.rave.org/tingle/language.txt if you want to read more, but
here ends the part about which I would appreciate comments by Real
Programmers most.

Does this system, in which composition and scoping are decoupled,
solve real problems that you have encountered ? Or more to the point;
does it solve enough problems to justify the rather heavy scoping
rule ? Toy or tool, that is the question...

Dirk van Deun
--
Ceterum censeo Redmond delendum