motivating <| as structure representation, not operator

Claus Reinke claus.reinke at
Thu Nov 17 07:57:27 PST 2011

I've been in two minds about <| . On one hand, it seems
useful and concise, on the other hand, it has odd properties
for an operator, such as "literal rhs only", "modify rhs". 

Both oddities could be addressed if TC39 would be willing 
to tackle object cloning. Then, <| could produce a clone of
its rhs. But cloning with hidden and private state is tricky
and might create new oddities while solving old ones. I'm
not sure about this (see PS).

Meanwhile, perhaps we can find another route for <|. 
When I first looked at it, I thought: isn't that just a way to 
avoid naming that [[prototype]] field?

If we could name it, we could write

    { [[prototype]]: proto , .. }

instead of 

    proto <| {..}

Given the prototype chain, it makes sense to give this a special
syntactic form, to distinguish the chain from other fields, but 
what if we just read the latter as sugar for the (unwritable) former? 

Then, <| would no longer be an operator, but would be what 
other functional languages call a data constructor (it directly
represents a data structure, with two substructures). It would 
not have to update the literal's implicit [[prototype]] field, 
because it would represent an explicit, unnamed [[prototype]] 
field [*2]. It would be part of writing the literal, not taking an 
object as rhs argument. 

We could even use <| in "destructuring" [*1]:

    function getProto( proto <| obj ) { return proto }

Ultimately, we might be able to talk about prototype chains in
terms of <| only, without the need for [[prototype]] or __proto__
(I'm thinking about blog posts here, which typically face the 
dilemma of talking about [[prototype]] vs 'prototype', to help
readers in the long run while confusing them at first, or talking 
about __proto__, to be understood in the short run, with possible 
problems later).

This wouldn't account for the specified special behavior if
the rhs is a function expression, but I'm not sure that is a
bad thing:

Currently, "new" takes the new object's [[prototype]] from 
the function's 'prototype', which is always fun to explain
(not), and the special casing in <| tries to account for that 
(if I understood that correctly, that is!-). 

Instead of adding complexity to <| to account for existing
complexity in "new", we might be able to use <| to clarify
that whole dance:

    function A(x) { this.x = x; return this }
    function B(x) { return (proto <| {x:x}) }
    new A(1) results in:  A.prototype <| {x:1} 
    new B(1) results in:  proto <| {x:1} 

[*1] let (proto <| dict) = obj
        // is 'dict' the same as 'obj', without [[prototype]]?
        // would 'dict' be a dictionary?
        // would 'dict' be a shared part of 'obj', or a clone?
        // would this form of cloning be problematic?

[*2] for backwards-compatibility, an unwrapped { .. } 
        would have to mean (Object.prototype <| { .. }).
        That context-sensitivity feels slightly awkward.

- .. others? Any serious flaws?-)

Would this view of <| as part of the object representation
be helpful? Could it be made to cover all use cases of the
current proposal?


I do think that TC39 should address object cloning, even if it 
is just a strawman listing the design options and problems,
as recently summarized on this list. Then one can make design
decisions based on "cloning would/would not solve this", not
based on "cloning is/is not tricky".

Shallow clones are what libraries can implement, so that
seems to be sufficient for many use cases. But then there
are HTML5 structured cloning, or transferring JS objects
between computation units not sharing a common heap.
Or being able to serialize arbitrary JS objects for storage.

More information about the es-discuss mailing list