Traits library

Brendan Eich brendan at mozilla.com
Wed Feb 17 21:29:05 PST 2010


On Feb 17, 2010, at 4:30 PM, Mark S. Miller wrote:

> Tom & I started with the question "If we could have any syntactic  
> sugar we wanted, how would we express traits?". Once we had a  
> plausible answer, we asked "How close could we come with a pure  
> library, with no new syntactic sugar?". The answer was, so close  
> that the remaining benefit of additional syntax was tiny, so we  
> dropped it.

First, kudos for traits.js, it has the long-sought-after (even back in  
ES4 days, with "fixtures") ability to specify fixed sets of methods  
and properties that can be composed freely, but which cannot be  
overridden without extra effort (if one allows that calling override  
is somehow "extra" compared to calling compose). Great work.

Second, demurral from the claim that the benefits of syntax are tiny,  
for either users and implementors.

Users have to put up with Trait.object({}) instead of something like  
trait {} (without an "extends" clause), in the following example from  
your mail (revised to use const to create a frozen function with  
frozen prototype bound to a block-scoped const binding):

     const Point(privX, privY) {
       let privInstVar = 2;
       const privInstConst = -2;
       let pubInstVar = 4;
       const pubInstConst = -4;
       return Trait.object({
         toString: function() {
           return ('<' + this.getX() + ',' + this.getY() + '>');
         },
         getX: function() { return privX; },
         getY: function() { return privY; },
         get pubInstVar() { return pubInstVar; },
         pubInstConst: pubInstConst
       });
     }

This is non-trivially noisy, but admittedly less of an issue (in this  
example, at any rate) for users than it is for implementors,  
considering that graphics-intensive code might make 1e6 Points, with  
3e6 bound methods (if I'm counting correctly).

Yet the syntactic noise and unchecked-till-runtime boilerplate tax  
remain an issue for users, and the Object.freeze calls add to this  
user-facing complexity if you revert the const Point(...) {...} change  
to use a function definition.

Syntax errors instead of runtime typo complaints are always a win for  
users.

If you keep that new const syntax, then there's no backward- 
compatibility argument against better trait syntax, by which users  
could say what they mean more simply and correctly, and  
implementations could optimize much more easily.

For implementors as well as bean-counting users, optimizing bound  
methods can be important. JS is being used to push pixels, and not  
just pixels: the scene graph, physics, and game logic all want speed  
at scale. It's a safe bet that programmers won't use traits only "in  
the small".

Your posts seem to ask or suggest about implementations pattern- 
matching for layered applications of Object.freeze and  
Function.prototype.bind, which is a tall order in itself.

But even doing such matching is pessimistic if it results in a "bound  
method pair" (meth, self) for a given method value (meth) and receiver  
object (self), created and frozen at the point where traits.js calls  
freeze(bindThis(meth, self)).

Bound methods need not be reified unless extracted as funargs (even if  
only as operands of ===, e.g.), or frozen unless tampered with. If  
they're (typically, or only ever) called on the right receiver, then  
the cost of creating a bind result can be avoided.

Optimizing to defer layered freeze/bind combinations until method  
value extraction or mutation attempt requires more than fancy pattern- 
matching on the part of an implementation. It requires a read barrier.  
Getters already require such a barrier, but it is on the slow path.  
The overhead of a method  binding/freezing barrier could be hidden in  
that slow path, but it is no picnic for implementors even if they have  
explicit, unambiguous syntax.

You could say "that's why implementors make the big bucks" (I've  
written this in the past, half in jest).

But I'm not concerned only about a once-per-implementation-effort tax  
on implementors who wish to avoid 3e6 methods per point, or other such  
poor performance scaling. I'm more concerned about complexity, which  
breeds bugs (including exploitable ones if the implementor is working  
in an unsafe language).

Put together the user and implementor taxes, and you have sufficient  
cause for new syntax.

Add to this tax revolt the plain desire for better syntax-as-user- 
interface. If you want const f(){}, why //wouldn't// you want  
declarative trait syntax?

/be


More information about the es-discuss mailing list