Personal tools

Traits Proposal

From OpenLaszlo

This proposal is to add a simple form of compositional behavior to the LZX class model. The following description is from the Software Composition Group at the University of Berne:

Traits are a simple composition mechanism for structuring object-oriented programs. A Trait is essentially a parameterized set of methods; it serves as a behavioral building block for classes and is the primitive unit of code reuse. With Traits, classes are still organized in a single inheritance hierarchy, but they can make use of Traits to specify the incremental difference in behavior with respect to their superclasses.
Unlike mixins and multiple inheritance, Traits do not employ inheritance as the composition operator. Instead, Trait composition is based on a set of composition operators that are complementary to single inheritance and result in better composition properties.

This proposal is specifically for the introduction of traits into the LZX object model. How LZX traits are implemented in JavaScript will be covered in a separate proposal describing the new OpenLaszlo JavaScript object model.


Contents

Contributors

Jim Grandy wrote this proposal, with advice and consent from P T Withington and Adam Wolff

Requirements

  • support instance-first development
  • support class-based development
  • traits should support all non-inheritance features of classes:
    • attributes
    • methods
    • events and handlers
    • subnodes/subviews
    • states

Proposal

Declaring a Trait

A trait declaration is almost the same as a class declaration. Every attribute and element which is legal in a class declaration is legal in a trait declaration, except of course for @extends.

 <trait name="T1">
   <!-- Attributes are supported -->
   <attribute name="a" value="2"/>
 
   <!-- Methods are supported, even "special" ones like construct and init -->
   <method name="froba">
     this.a=4;
   </method>
  
   <!-- Events and handlers are supported, including onconstruct and oninit -->
   <event name="onfoobar"/>
   <handler name="onfoobar">
     this.setAttribute("a", 5);
   </handler>
 
   <!-- Subnode and subview hierarchies are supported -->
   <view>
      <view/>
   </view>
 
   <!-- States, layouts and other primitive elements are supported, too -->
 
 </trait>

Traits may not be directly instantiated, although the same effect may be achieved by declaring an instance of a class and using the @traits attribute (see "Declaring an instance with traits," below).

Declaring a class with traits

Traits are mixed in to the class hierarchy using the @traits attribute.

<class name="B" extends="A" traits="T1" />

Trait subnodes are appended just like class subnodes, and participate in placement rules just like class subnodes.

Multiple traits are simply listed comma-delimited in the @traits attribute.

<trait name="T2" />

<class name="C" extends="A" traits="T1,T2" />

<C id="foo" />

Inheritance order is: instance (foo), class (C), traits (in order of declaration: T1, then T2), then superclass (A).

Declaring an instance with traits

In support of instance-first development, an instance can be customized by mixing in traits at the point of instantiation.

<A traits="T1, T2"/>

The augmented A instance declaration here has the same behavior and state as an instance of the C class declared in the previous section.

Trait introspection

b.traits; // list of instance's direct traits
B.traits; // list of class' direct traits
T1.traitname; // name of this trait

We don't supply a function to compute extended chain: easily built from @traits attribute above.

Examples

(TBD)

Discussion

Traits and States

Traits and states are similar in the sense the both are used compositionally to change the behavior of an object. One might even be tempted to posit that states should be re-imagined as agents that automatically apply and remove traits in response to specific triggers. Being able to unify the two concepts in this way has appeal: traits and the compositional part of states do indeed share a lot and states might benefit from the more rigorous definition of traits.

Unfortunately, this re-alignment doesn't work directly, or at least it doesn't work given the way states currently behave. Here's why.

In the Rejected Features section you'll find a proposal for dynamic application of traits to an object. This feature would allow us to add a trait to an object after it had been created, and then remove the trait again. The trait would be inserted in the inheritance path just after the instance and just before any other traits declared on the instance.

The idea of dynamically applying a trait is quite close to the concept in the Self programming language of dynamic delegation (see [1] for an overview).

In the case of states, we might imagine a similar mechanism: a unit of behavior is added to the inheritance path and can then be removed. However, the position in the path is different: the composition takes place in front of the instance, not behind it.

But that's not even the biggest reason this wouldn't work. It turns out states don't use inheritance at all: they act by substituting slots in the target object rather than interposing an inheritance layer in front. States literally save off the original slots and write new slots, unwinding the process upon removal.

It could be argued that this is a problematic formalism, and that we'd be better off with something closer to dynamic delegation. Performance requirements may in fact push us in that direction -- but it's also possible that performance requirements will push us away from mucking with object structure at all at runtime. Because of the uncertainties here, this proposal does not include runtime-applied traits and does not attempt to unify traits with states.

Rejected Features

Dynamically applying a trait

A trait may be dynamically applied to, and removed from, an instance. The effect is as if the instance had been declared with the additional trait at the front of its @traits list. A trait can only be removed if it has been dynamically applied.

b.applyTrait(T3);
b.removeTrait(T3);

b.removeTrait(T2); // ILLEGAL - T2 is a trait of class B

b.applyTrait(T2);  // ILLEGAL - T2 is already a trait in extended chain

@requiresclass

<trait name="T3" requiresclass="A" />

Compile or runtime error if T3 is applied to a class/instance that doesn't derive from given class.

<class name="C" extends="node" traits="T3"/> 

<class name="D" extends="A" traits="T3"/> 

ptw 18:11, 25 Mar 2006 (EST)

This mechanism means that you can only require a superclass. Often in mix-ins it is the subclass that implements the required interface for the trait. In Lisp, you specify which methods and attributes are required and get an error if you try to instantiate a class without all requirements being fulfilled.

jgrandy 5 April 2006

Good point, but I was thinking of the trait's requirements. For example, you might have a "drawable" trait that only works for subclasses of view and shouldn't be used with node. Perhaps it relies on view's interface. This is cheap, but also fragile -- but the alternative, that we add requirements on methods and attributes (and subnodes?), is a pretty big job. I'm willing to defer this feature if necessary, though.

jgrandy 19 May 2006

Moved to Rejected category. Let's see what a higher-level requires/provides mechanism looks like before considering whether this is necessary.

what about trait extension?

<trait name="T2" extends="T1" />

Would add T1 in traits chain before T2 if it doesn't already exist in extended traits chain at point of placement.

Skip this -- too complex and hard to tell where a trait would come in the extended chain. We should be able to get the same effect using requires/provides.

Implementation Notes

An experimental implementation of traits, supporting just traits placed between a class and its superclass, has been checked into the 3.x trunk sources. This implementation creates interstitial anonymous classes corresponding to each trait declaration within a class definition.

References

Recent work on traits has been done in the Software Composition Group, part of the Institute of Computer Science and Applied Mathematics (IAM) at the University of Berne. Their Traits page contains a wealth of information about semantics, applications, and implementation in various programming languages. One such language is Scala.

The idea is fairly old, though. Bracha & Cook, OOPSLA '90 describes something much like traits.

In addition, David Herman listed these references on the es4-discuss list: