Proposal: non-frozen trait instances with early trait-conformance checks

6 views
Skip to first unread message

Tom Van Cutsem

unread,
Oct 11, 2010, 2:37:33 PM10/11/10
to traits-js, Brendan Eich, David Herman, Erik Arvidsson, Alex Russell, Mark S. Miller, Sam Tobin-Hochstadt
By popular demand (this issue was raised by Irakli earlier on this mailing list, and also at the recent ECMA TC39 meeting):

Currently, in traits.js, Trait.create(proto, trait) generates a frozen object whose methods have their |this| bound to the instance. This ensures that trait instances are tamper-proof. OTOH, it makes trait instances unsuitable as prototypes for other objects and it implies generating per-instance bound-methods, which is costly.

This can be avoided by creating a trait instance using Object.create rather than Trait.create. However, since Object.create is oblivious to trait semantics, it will not throw on incomplete traits (missing required properties) or conflicts (lingering conflict properties).

MarkM did make the good observation that even when instantiating traits using Object.create, there is a lazy form of conflict detection: if code ever calls a conflicting property, the property will be bound to a method that throws, signaling the conflict. Hence, even Object.create supports conflict detection, it just postpones it to the latest possible moment (which is in line with the rest of Javascript's dynamic nature).

Nevertheless I hear a strong call for a "third trait constructor" that would perform the instantiation-time checks of Trait.create, but returns an "open" (non-frozen,non-bound) object such as one defined by Object.create. Here is one proposed (and upwards compatible) change to the traits.js library that would cater to such a "third constructor":

Suppose Trait.create has an additional, optional "options" argument. The library could introduce an option named "open" that controls the freezing/binding behavior of Trait.create:

var instance = Trait.create(proto, aTrait, { open: true });

This call to Trait.create would fail on missing required and lingering conflicting properties or otherwise return a non-frozen trait instance whose methods are not bound. This allows clients to use the instance as a prototype, for efficient method sharing between other instances, as in:

function Foo(state) {
  this.mystate = state;
};
Foo.prototype = Trait.create(proto,trait,{open:true});

var a = new Foo(42);
var b = new Foo(24);
// a and b share the methods defined by 'trait'

"open" would default to "false", preserving Trait.create's current semantics.

Thoughts?

Cheers,
Tom
Reply all
Reply to author
Forward
0 new messages