three small proposals: the bikeshed cometh!

Alex Russell alex at dojotoolkit.org
Thu Apr 29 12:11:13 PDT 2010


On Thu, Apr 29, 2010 at 10:03 AM, Brendan Eich <brendan at mozilla.com> wrote:
> On Apr 29, 2010, at 12:25 AM, Alex Russell wrote:
>
> Some small, pre-colored panels for the shed. Given that these are mostly
> matters of syntax and not semantics, please believe me when I suggest that
> the warts discussed herein present sharp edges that should be rounded off by
> the committee -- not because they're interesting in any way but because the
> primary users of the language are shipping (bad) fixes around the network
> billions of times a day. Such rank inefficiency needs sunset date.
>
> This is not just about syntax. Let's ignore the threat of being accused of
> bikeshedding and evaluate holistically.
>
> Summary of proposals:
> ---------------------
>
> // 1.) Really generic generics
>
> ArrSubtype = function() { };
> ArrSubtype.prototype = Object.create(Array.prototype);
>
> var nas = new ArrSubtype();
> nas.push("howdy", "pardner");
>
> nas.slice(0) instanceof Array; // currently true
> nas.slice(0) instanceof ArrSubtype; // will be true after fix
>
> An incompatible change, but would it break much code? Hard to say without
> trying it at scale. Here are some codesearch results:
> http://www.google.com/codesearch?hl=en&lr=&q=%22prototype+%3D+[]%3B%22+lang%3Ajavascript&sbtn=Search
> http://www.google.com/codesearch?hl=en&lr=&q=%22prototype+%3D+new+Array%3B%22+lang%3Ajavascript&sbtn=Search
> How would it work exactly? More below on "subtyping".
>
> // 2.) Shorthand for "function", aka the "not lambda"
>
> node.addEventListener("click", #(e){ e.preventDefault(); });
> node.removeEventListener("click" obj!#(e){ ... }); // see #3
>
> The hash or number sign is not obviously all about functions. I've tried out
> the other alternatives in recent talks. No one is really enthusiastic about
> any of
> λ foo() (bar + baz)
> ƒ foo() (bar + baz)
> \foo() (bar + baz)
> (the foo name is optional but should be expressible).
> The Greek lowercase lambda is actually an incompatible change from ES3
> (perfectly legal identifier there). It's also hard to type on most
> keyboards.
> The florin is easier (alt-f on my Mac) but maybe a bit visually light and
> hard to scan for quickly, and it's arguably harder for newcomers to divine
> its meaning.
> The \ does not make much sense, but it was proposed first among all of these
> on es-discuss, IIRC.
> One wag replied after I had people vote on these "what about voting on
> function"? Many hands then went up, more than for any of the above. This set
> the cat among the shorthand-promoting pigeons.

None of the options you provided had the contours of the proposed
solution, so it's not clear that the lack of support for your
proposals commutes to lack of support for this one.

> // 3.) Shorthand for Function.prototype.bind
>
> var boundMethod = obj!method;
> node.addEventListener("click", obj!method);
> node.removeEventListener("click", obj!method);
>
> There's some precedent for ! as non-blocking send, returning a promise.

In ES? Which implementation? And is that the only objection?

> NodeList.prototype.parents = function() {
>    // should return a NodeList
>     return this.map(function(n) { return n.parentNode; });
> }
>
> IIRC a NodeList is a "live array", sort of a query-as-array or cursor that
> is continuously updated when the DOM mutates. It's really not an Array.

Not today, but changes like this set the groundwork for turning
NodeLists into real arrays. More to the point, what's so funky about a
"live array" anyway? All Array objects in the language are "live". The
idea that you'd have some array that's *not* subject to mutation is
the oddball, not the rule.

> * Kill code in libraries that exists only to wrap built-in methods thanks to
> existing mis-specification of generics
> * Sub-types of Array suffer many warts, but making Array.prototype methods
> return instances of subtypes will allow DOM-side changes to make subtyping
> much more natural in real-world systems
>
> "Subtype" is not well-defined in JS. Prototype-based delegation is not the
> <: relation from type theory, because of mutation, both of the prototype
> object and of the future binding of f in a scope and f.prototype in
> user-defined function f.
> This isn't just a pedantic point. If we don't have a well-defined relation,
> how can we evaluate proposals that want to improve support for that
> relation, whatever it is?

I'm not claiming that we should do anything about types in this
proposal. I'm explicitly ONLY asking that when you call one of the
Array generics through an object that has a mutable length property
and some agreed upon way to add elements (push?), that they return new
isntances of the caller's type, not Array.

That's *all* I'm asking for. Discussion of types, relationships
between types, etc. is a distraction from the actual proposal and I'm
sorry I ever used the word "subtype".

> In this case it seems to me you might want the result of Array generics to
> be created by calling (new this.constructor). For nas.slice(0), the generic
> slice code would then put elements got from |this| into the result of (new
> this.constructor). Is this the spec you want?

Yes.

> If so, it seems like an improvement, but again constructor has low integrity
> (none for user-defined constructor functions) without freeze, so there's no
> subtype relation in the formal <: sense. Still, it seems to me an
> improvement, ignoring the incompatibility.

Great!

>  // equivalent:
>  function(){ return 10; }
>  #(){ return 10; }
>  #{ return 10; } // no args, optionally elide ()
>
> This does several things at once, and we have discussed one of them: turning
> the completion value of the body into the return value.
> The objection to this, voiced clearly by Waldemar, is the unintended
> completion value in tail position leaking out as a return value. This is
> hard to see and test for, and it requires ugly void operator usage, or a
> dummy final value, to control.

I'm somewhat ambivalent about it. Killing "function" is a better deal
than eliding "return", but doing away with both seems useful when
you're in a new form (the "#" function) that can be known to have
those semantics.

> Dave Herman has recently
> proposed http://wiki.ecmascript.org/doku.php?id=strawman:let_expressions,
> which include an explicit "completion value here" prefix: => 10; in tail
> position would result in 10, and without => the result would be the
> undefined value.

Seems reasonable.

> * equivalent semantics to the function(){} syntax
>
> No, not equivalent because tail position completion values are not return
> values with functions. Again semantics matter, not just syntax.

As noted, that's optional. I'm not tied to it. All I care about for
this proposal is to shorten "function" to "#". The other options where
added there as (apparently unclear) illustrations of how other changes
might make such a syntax even better.

> 3.) Syntax for bound function de-reference
> [snip] So why does this suck? Two reasons: it's long-ish to type, and it
> doesn't do what the dot operator does -- i.e., return the same function
> object every time.
>
> The lack of memoization is a good point. I've written at length about the
> challenges for implementations to "join" function objects as an
> optimization:
> https://mail.mozilla.org/pipermail/es-discuss/2010-February/010830.html
> https://mail.mozilla.org/pipermail/es-discuss/2010-February/010832.html

One point I didn't bring up was that the new bang operator doesn't
allow for partial argument application, ala Function.prototype.bind.
Seems a minor loss to me since all the other syntaxes I could think up
that would have allowed it feel cluttered.

> What to do? We propose the bang operator -- ! -- as a new form of binding
> de-reference of function objects. It's one character and even includes a
> dot.  It has the following additional properties:
>
> 1.) all de-references via bang from an object/function pair return the
> *same* function object (modulo #4).
> 4.) bang-bound functions are weakly referenced. If GC would otherwise remove
> a function object from memory, having previously bang-bound a function
> should not keep the function object alive
>
> This is a tricky area. We have experience with such ephemeral objects.
> Problem is, sometimes users decorate them with ad-hoc properties
> ("expandos"), which by your proposed rules will not keep them alive. The GC
> runs, and the decorator is surprised to see the ad-hoc properties gone.

Yeah, I considered it. I'd be just as fine with wrappers having
bound-to object lifetimes (as in my sample).

> Apart from the ! as promise-send and weak-vs.-expando issues, the big
> objection here is that you make each reference specify bound-ness and
> require memoization on the fly. An alternative would be to bind at method
> definition point. Then you could only extract the bound method, no matter
> how used, and via the conventional . operator. See
> http://wiki.ecmascript.org/doku.php?id=strawman:obj_initialiser_methods

This is really terrible. You don't know when you're going to get a
bound result or not, so as a user you're back to the same "guess what
kind of function it is and always call bind() anyway" games. The new
operator always ensures that you'll get *a* bound function, even if
what you're currently binding to isn't exactly what you think it is
(which is somewhat rare).

> i'm against "invoke-only" methods, but bound method definition syntax is
> easier for users and implementors to swallow, and preserves dot as the one
> operator needed for extraction. It would also make the method reference
> strong, which would avoid the too-weak loss of expando problem.

We can solve that without furthering the ambiguity of the dot
operator, which is the primary problem to be solved here. I don't
consider proposals that leave "dot" ambiguious with regards to binding
to be of any real use.

> Glad to see this proposed for discussion -- good suggestions, directionally
> and in some details.
> /be


More information about the es-discuss mailing list