New traits system
Brian Rice
water at tunes.org
Mon Nov 15 22:57:12 PST 2004
So, I'll present the new system of inheritance, how it differs from our
prior system, and explain part of the decision-making process that was
involved in choosing this new direction.
First, the object-traits direct slot relationship has been mediated by
an object-traitsWindow relationship, which I will explain shortly.
Previously, object inheritance was performed solely through creating
and setting delegation slots, and traits slots were not special in any
way. Traits slots were also traversed in depth-first order with the
slot-ordering performed from most-recently-added to
least-recently-added. Since this order was not explicitly visible or
easily controllable, performing addDelegate: on your newly derived type
of object would make delegate behavior the /primary/ behavior when
defining methods, i.e. @(Foo traits) would go to the delegated object's
traits object and not what was in Foo's #traits object (Note: this is
BAD). We have modified #slotNames and #delegateNames primitives to
answer slots in an array which reflects their ordering. We have also
added addDelegate:after: and addDelegate:before: (which position
relative to other slot names).
But this is not enough. Even with the ability to easily deal with
delegation order, there is still some complexity or confusion of levels
in the way that traits objects work. Basically each object's traits
object is delegating to its parent traits as well as Traits traits, its
own traits object, on the same level. It doesn't matter what the
delegation order is, because often one meaning of a selector will be
needed and then another on the same object. We hypothetically worked
with a few different delegation orders (breadth-first, topological),
and /all/ of them lead to problems with ambiguity of meaning.
So instead we talked about a "traits window": essentially each object
delegates to one of these which holds all of the /various/ traits
objects in order of precedence in slots, and these just delegate right
to Traits traits instead of each other. So we're talking about
composition now, not a multiple inheritance graph. And each object can
take its traits window and re-arrange the ordering to suit its behavior
better. So if you have a "diamond inheritance" shape, instead of having
to perform resends through local method overrides in order to get the
desired combination of behaviors and exclusion of others, you simply
(re-)order the visitation at the bottom of the diamond.
To make this work properly, a flag was added to maps to restrict
delegation: if dispatch sees an object with the flag set, it will not
traverse any subsequent objects it finds with the flag set. This flag
may be set in userland, and in fact the normal derivation routines will
set this flag for a traitsWindow's map. So within a single lookup, an
object-traitsWindow relation be traversed at most once. This avoids
issues where Traits traits definitions are reachable from any object
(e.g. accessing #clone method definitions from Oddballs), but it's
userland controlled so it may be used anywhere where such a tower needs
to be avoided.
And what does this mean to all of you? Not a whole lot, thankfully. :-)
The protocols for addPrototype:derivedFrom: and derive itself are the
same, but create more sensibly-behaving results. You have the added
control that each prototype (via its traitsWindow) can go about
re-ordering its delegation order regardless of its parents.
When you define a method on "Foo traits", it still goes to the right
object, but that is reached via the traitsWindow, and basically
represents "how I'm overriding my ancestors". From a broader
perspective, all of the behavior can be put into re-usable parts, and
then the "Foo traits" of the world be used just where compositions of
these parts don't line up correctly. This is analogous to the Squeak
research on traits as a way to safely add composable multiple
inheritance to single-inheritance class-based OOP.
*** The Bottom Line ***
Everything is the same as it used to be in terms of inheritance
capabilities and the regular grammar, but in order to determine lookup
patterns, you don't have to mentally traverse the inheritance graph;
instead you can just look at the traitsWindow and re-order it if you
don't like it, on a case-by-case basis.
I'll spend some more time refining this for the manual with some
examples, and I'm sure we'll find convenient ways to manage this as we
go.
--
Brian T. Rice
LOGOS Research and Development
http://tunes.org/~water/
More information about the Slate
mailing list