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