three small proposals: the bikeshed cometh!

# Alex Russell (14 years ago)

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.

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

// 2.) Shorthand for "function", aka the "not lambda"

node.addEventListener("click", #(e){ e.preventDefault(); }); node.removeEventListener("click" obj!#(e){ ... }); // see #3

// 3.) Shorthand for Function.prototype.bind

var boundMethod = obj!method; node.addEventListener("click", obj!method); node.removeEventListener("click", obj!method);

Discussion:

1.) Really generic generics

Methods on Array.prototype that today return instances of Array should
instead return instances of whatever subtype begat them. Assume that
the DOM sucked less and that NodeList instances were subtypes of
Array. In that case, we should be able to do something like:

NodeList.prototype.parents = function() { // should return a NodeList return this.map(function(n) { return n.parentNode; }); }

document.querySelectorAll("p").slice(2).parents().filter(...);

Today, the only way for subtypes to use these generics is to wrap or re-implement them. The functions that need to be fixed include:

map, filter, splice, slice, concat

Rationale:

  • 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

2.) Function shorthand

The most commonly-used object types in JavaScript have syntactic sugar:

new Array(...) == [ ... ] new Object(); ... == { .... }

Why leave functions out of the party? Lambda syntaxes have been
discussed at great length with most of them either failing to remove
the "function" wart or giving up multi-line or visual delineation
properties. We instead propose just shortening the word "function".
Several variants are possible:

// equivalent: function(){ return 10; } #(){ return 10; } #{ return 10; } // no args, optionally elide ()

// lambda-style return #{ 10; }

// makes for easy-to-read operations: [ ... ].map(#(i){ i+10; });

Rationale:

  • less typing, particularly when supplying Array generic and DOM
    callbacks
  • equivalent semantics to the function(){} syntax
  • able to work as a short-ish lambda-like declaration without large
    changes
  • "JavaScripty"; keeps the existing brace rules, keeps multi-line
    behavior, and helps to visually delimit start/end of function object.

3.) Syntax for bound function de-reference

Many functions, both in the DOM and in the library, accept functions
as arguments. ES5 provides a bind() method that can ease the use of
these functions when the passed method comes from an object. This is,
however, inconvenient. E.g.:

node.addEventListener("click", obj.method.bind(obj, ...));

Also, insufficient. Event listeners can't be detached when using
Function.prototype.bind:

// doesn't do what it looks like it should node.removeEventListener("click", obj.method.bind(obj, ...));

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.

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). 2.) non-function objects de-referenced via bang are not mangled in
any way (identical to dot-operator de-reference) 3.) there's no provision for binding arguments (curry) 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

As from the intro:

node.addEventListener("click", obj!method); // actually removed without extra book-keeping or wrappers! node.removeEventListener("click", obj!method);

And a small (untested) version of the behavior as a function written
in ES5 (without weakref behavior):

var bang = function(obj, func) { var fn = obj[func]; // Still no isFunction...sigh if (Object.prototype.toString.call(fn) === "[object Function]") { if (!obj["_bound"]) { Object.defineProperty(obj, "_bound", { value: {}, enumerable: false, configurable: false, writable: false }); } var bound = obj._bound[func]; if (!bound) { bound = obj._bound[func] = fn.bind(obj); } return bound; } else { // not a function object, just return the deref return fn; } }; // ES3 impl lacks nice syntax: node.addEventListener("click", bang(obj, "method")); // works! node.removeEventListener("click", bang(obj, "method"));

Thoughts?

# Garrett Smith (14 years ago)

On Thu, Apr 29, 2010 at 12:25 AM, Alex Russell <alex at dojotoolkit.org> 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.

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

The Array prototype methods are not going to change the return type. Doing that would break any existing code that is trying to use array generics and expecting the this value to be an array.

// 2.) Shorthand for "function", aka the "not lambda"

node.addEventListener("click", #(e){ e.preventDefault(); });  node.removeEventListener("click" obj!#(e){ ... }); // see #3

That would be possible so long as it does not conflict with existing syntax.

AFAIK, "#" is used in Spidermonkey for recursive object literal notation:

#1={a:#1#}

  • however it would not seem to conflict here.

[...]

Discussion:

1.) Really generic generics

Methods on Array.prototype that today return instances of Array should instead return instances of whatever subtype begat them. Assume that the DOM sucked less and that NodeList instances were subtypes of Array. In that case, we should be able to do something like:

If a thought about code cannot be explained using correct terminology, then it is not very well understood by the person doing the explaining.

I inferred from your example that you meant "an object that has Array.prototype in its prototype chain".

The use of nonstandard terminology to describe ECMAScript tends to harbor misconceptions about the language.

On that, NodeList is not - and should not be - an Array. The two concepts should be understood independently before attempting to use them. This very subject was just discussed on WHAT WG list. Did you see it? Here is the last message:

www.mail-archive.com/[email protected]/msg21131.html

Elsewhere: "whatwg] WebIDL and HTML5" lists.w3.org/Archives/Public/public-webapps/2008JulSep/0480.html

NodeList.prototype.parents = function() {    // should return a NodeList     return this.map(function(n) { return n.parentNode; });  }

document.querySelectorAll("p").slice(2).parents().filter(...);

Today, the only way for subtypes to use these generics is to wrap or re-implement them. The functions that need to be fixed include:

No functions need to be included.

[...]

2.) Function shorthand

[snip]

Many functions, both in the DOM and in the library, accept functions as arguments. ES5 provides a bind() method that can ease the use of these functions when the passed method comes from an object. This is, however, inconvenient. E.g.:

node.addEventListener("click", obj.method.bind(obj, ...));

Also, insufficient. Event listeners can't be detached when using Function.prototype.bind:

That is not true at all. I suggest reading the ES5 specifation that before proceeding.

[...]

Garrett

# Alex Russell (14 years ago)

[ snip ]

# Mike Shaver (14 years ago)

On Thu, Apr 29, 2010 at 3:25 AM, Alex Russell <alex at dojotoolkit.org> wrote:

3.) Syntax for bound function de-reference

Many functions, both in the DOM and in the library, accept functions as arguments. ES5 provides a bind() method that can ease the use of these functions when the passed method comes from an object. This is, however, inconvenient. E.g.:

node.addEventListener("click", obj.method.bind(obj, ...));

node.addEventListener("click", #(e){obj.method(e)});

1.) all de-references via bang from an object/function pair return the same function object (modulo #4).  2.) non-function objects de-referenced via bang are not mangled in any way (identical to dot-operator de-reference)  3.) there's no provision for binding arguments (curry)  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

I'm not sure how you make 1 and 4 happen at the same time. (It also sounds like you're saying that bang-bound functions weakly reference their inner function, not that they are themselves weakly referenced.)

Mike

# Alex Russell (14 years ago)

On Thu, Apr 29, 2010 at 4:15 AM, Mike Shaver <mike.shaver at gmail.com> wrote:

On Thu, Apr 29, 2010 at 3:25 AM, Alex Russell <alex at dojotoolkit.org> wrote:

3.) Syntax for bound function de-reference

Many functions, both in the DOM and in the library, accept functions as arguments. ES5 provides a bind() method that can ease the use of these functions when the passed method comes from an object. This is, however, inconvenient. E.g.:

node.addEventListener("click", obj.method.bind(obj, ...));

node.addEventListener("click", #(e){obj.method(e)});

...which i then can't get a reference to for disconnection. But point taken.

1.) all de-references via bang from an object/function pair return the same function object (modulo #4).  2.) non-function objects de-referenced via bang are not mangled in any way (identical to dot-operator de-reference)  3.) there's no provision for binding arguments (curry)  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

I'm not sure how you make 1 and 4 happen at the same time.

That's the weakref bit. If GC happens and the only thing with a handle is the internal bang binding, the generated function object should be released. If something else holds onto it strongly and you ask for the bang-bound variant again, it should return the object that was returned previously.

(It also sounds like you're saying that bang-bound functions weakly reference their inner function, not that they are themselves weakly referenced.)

Yeah, sorry for not being clear.

# Brendan Eich (14 years ago)

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:

www.google.com/codesearch?hl=en&lr=&q="prototype+%3D+[]%3B %22+lang%3Ajavascript&sbtn=Search www.google.com/codesearch?hl=en&lr=&q="prototype+%3D+new+Array%3B"+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.

// 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.

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.

  • 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?

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?

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.

// 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.

Dave Herman has recently proposed 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.

  • 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.

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:

esdiscuss/2010-February/010830, esdiscuss/2010-February/010832

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.

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

strawman:obj_initialiser_methods

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.

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

# Tom Van Cutsem (14 years ago)

Allow me to make the case that harmony proxies can ease some of your (syntactic) pains:

// 2.) Shorthand for "function", aka the "not lambda"

node.addEventListener("click", #(e){ e.preventDefault(); }); node.removeEventListener("click" obj!#(e){ ... }); // see #3

If what you're looking for is simply a convenient shorthand, Harmony proxies may partially satisfy your needs. See: < harmony:proxies#higher-order_messages

Basically, the expression _.foo(x) returns a function(receiver) { receiver.foo(x) } such that your first example would become:

node.addEventListener("click", _.preventDefault() );

// makes for easy-to-read operations:

[ ... ].map(#(i){ i+10; });

Ah, if only '+' were a message, then you'd be able to write: [ ... ].map( _.+(10) )

Nevertheless: the pattern array.map(function (e) { e.someMessage() }) should be common enough such that it can benefit from the '_' shorthand.

3.) Syntax for bound function de-reference

Using a similar trick as above + MarkM's proposed Ephemeron Tablesstrawman:ephemeron_tablesto

enable memory-leak-free caching of bound functions, a small adaptation of your function results in:

// a map of obj -> (fun -> boundfun)

var _bound = new EphemeronTable();

var bang = function(obj) { return Proxy.create({ get: function(_bang, func) { var fn = obj[func]; // Still no isFunction...sigh if (Object.prototype.toString.call(fn) === "[object Function]") { var objBoundFuns = _bound.get(obj); if (objBoundFuns && objBoundFuns.get(fn)) { return objBoundFuns.get(fn); // bound function already existed } else { // create a new bound function var bound = fn.bind(obj); if (objBoundFuns) { // obj already has bound functions, add it to the existing map objBoundFuns.set(fn, bound); } else { // create a new mapping from obj -> bound functions of obj objBoundFuns = new EphemeronTable(); objBoundFuns.set(fn, bound); _bound.set(obj, objBoundFuns); } return bound; // return new bound fun } } else { // not a function object, just return the deref return fn; } } }) };

node.addEventListener("click", obj!method);

// actually removed without extra book-keeping or wrappers! node.removeEventListener("click", obj!method); [...] // ES3 impl lacks nice syntax: node.addEventListener("click", bang(obj, "method")); // works! node.removeEventListener("click", bang(obj, "method"));

Now consider (I renamed 'bang' to 'bind', as it seems more sensible):

node.addEventListener("click", bind(obj).method ); node.removeEventListener("click", bind(obj).method );

Granted, the overhead compared to dedicated syntax is probably significant. Then again, the fact that this pattern avoids dedicated syntax while remaining concise is also significant :-)

# Mike Samuel (14 years ago)

2010/4/29 Brendan Eich <brendan at mozilla.com>:

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: www.google.com/codesearch?hl=en&lr=&q="prototype+%3D+[]%3B"+lang%3Ajavascript&sbtn=Search, www.google.com/codesearch?hl=en&lr=&q="prototype+%3D+new+Array%3B"+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.

I'm not arguing for lambda since it is a pain to type, but this is not true if you make it a restricted production, and make the name non optional <lambda> (no line separator) <identifier>

cannot appear in a valid ES3 program.

# Brendan Eich (14 years ago)

On Apr 29, 2010, at 11:34 AM, Mike Samuel wrote:

2010/4/29 Brendan Eich <brendan at mozilla.com>:

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.

(What mailer are you using that mixes up citation levels so badly?)

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.

I'm not arguing for lambda since it is a pain to type, but this is not true if you make it a restricted production, and make the name non optional <lambda> (no line separator) <identifier> cannot appear in a valid ES3 program.

Yeah, true -- but then that's a different proposal.

Requiring the name loses for most use-cases and users, as far as I can
tell.

[massive tail overcite trimmed -- at least it was not topcited :-P.]

# Brendan Eich (14 years ago)

On Apr 29, 2010, at 11:34 AM, Mike Samuel wrote:

I'm not arguing for lambda since it is a pain to type, but this is not true if you make it a restricted production, and make the name non optional <lambda> (no line separator) <identifier> cannot appear in a valid ES3 program.

I forgot to note that many on TC39 are against adding more restricted
productions. The ones we have are quite enough!

# Garrett Smith (14 years ago)

---------- Forwarded message ---------- From: Garrett Smith <dhtmlkitchen at gmail.com>

Date: Thu, Apr 29, 2010 at 11:44 AM Subject: Re: three small proposals: the bikeshed cometh! To: Alex Russell <alex at dojotoolkit.org>

On Thu, Apr 29, 2010 at 3:01 AM, Alex Russell <alex at dojotoolkit.org> wrote:

[ snip ]

Many functions, both in the DOM and in the library, accept functions as arguments. ES5 provides a bind() method that can ease the use of these functions when the passed method comes from an object. This is, however, inconvenient. E.g.:

node.addEventListener("click", obj.method.bind(obj, ...));

Also, insufficient. Event listeners can't be detached when using Function.prototype.bind:

That is not true at all. I suggest reading the ES5 specifation that before proceeding.

From 15.3.4.5:

4. Let F be a new native ECMAScript object .

I can't discern any particular reason why equivalence should succeed for multiple calls to bind() from the spec. What am I missing?

The bound function that is returned can be saved as a property of something, so that it can be later removed.

var f = obj.method.bind(obj); node.addEventListener("click", f, false); node.removeEventListener("click", f, false);

Garrett

# Mark S. Miller (14 years ago)

On Thu, Apr 29, 2010 at 12:25 AM, Alex Russell <alex at dojotoolkit.org> wrote:

node.addEventListener("click", bang(obj, "method")); // works! node.removeEventListener("click", bang(obj, "method"));

On Thu, Apr 29, 2010 at 10:42 AM, Tom Van Cutsem <tomvc at google.com> wrote:

node.addEventListener("click", bind(obj).method ); node.removeEventListener("click", bind(obj).method );

Both techniques cache the bound method, in order to preserve identity, so that the above examples work.

When caching, one should always worry about cache invalidation. Say that obj inherits from Foo.prototype and that both examples are thereby caching Foo.prototype.method as bound to obj. Neither Alex nor Tom are first verifying that Foo.prototype.method is a non-writable, non-configurable data property, and so neither are justified in assuming its value may not change. Between the two statements above, what happens if

Foo.prototype.method = function(){ /* some other function */ };

? Because Alex's cache is indexed by the identity of obj and by the name "method", bang will return a stale result and the removeEventListener will succeed when it shouldn't. Because Tom's cache is indexed by the identity of obj and the identity of the function being bound, the removeEventListener will properly fail.

I verbally verified with Tom just now that he hadn't thought about this cache invalidation issue at all. I think this corroborates that the EphemeronTable-based technique is more robust -- it did the right thing for the right reasons even for cases that weren't thought about at the time. (Of course, no matter how robust the patterns being used, please always think about cache invalidation when designing a cache of any kind.)

# Alex Russell (14 years ago)

On Thu, Apr 29, 2010 at 11:51 AM, Mark S. Miller <erights at google.com> wrote:

On Thu, Apr 29, 2010 at 12:25 AM, Alex Russell <alex at dojotoolkit.org> wrote:

node.addEventListener("click", bang(obj, "method"));  // works!  node.removeEventListener("click", bang(obj, "method"));

On Thu, Apr 29, 2010 at 10:42 AM, Tom Van Cutsem <tomvc at google.com> wrote:

node.addEventListener("click", bind(obj).method ); node.removeEventListener("click", bind(obj).method );

Both techniques cache the bound method, in order to preserve identity, so that the above examples work. When caching, one should always worry about cache invalidation. Say that obj inherits from Foo.prototype and that both examples are thereby caching Foo.prototype.method as bound to obj. Neither Alex nor Tom are first verifying that Foo.prototype.method is a non-writable, non-configurable data property, and so neither are justified in assuming its value may not change. Between the two statements above, what happens if     Foo.prototype.method = function(){ /* some other function */ }; ? Because Alex's cache is indexed by the identity of obj and by the name "method", bang will return a stale result and the removeEventListener will succeed when it shouldn't. Because Tom's cache is indexed by the identity of obj and the identity of the function being bound, the removeEventListener will properly fail. I verbally verified with Tom just now that he hadn't thought about this cache invalidation issue at all. I think this corroborates that the EphemeronTable-based technique is more robust -- it did the right thing for the right reasons even for cases that weren't thought about at the time. (Of course, no matter how robust the patterns being used, please always think about cache invalidation when designing a cache of any kind.)

I'm arguing for a syntax, not a particular implementation. The one I provided was only to illustrate (briefly, if that's at all possible) how such a thing might work. The last thing I want is an actual bang() or bind() method to be carted around the network in source form. That's the failure I'd like us to finally start avoiding.

# Mike Samuel (14 years ago)

2010/4/29 Brendan Eich <brendan at mozilla.com>:

On Apr 29, 2010, at 11:34 AM, Mike Samuel wrote:

I'm not arguing for lambda since it is a pain to type, but this is not true if you make it a restricted production, and make the name non optional   <lambda> (no line separator) <identifier> cannot appear in a valid ES3 program.

I forgot to note that many on TC39 are against adding more restricted productions. The ones we have are quite enough!

Yeah, I'm on the other side. I think that all new productions that introduce a new keyword should be restricted. We have enough semicolon insertion problems already! :)

# Alex Russell (14 years ago)

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: www.google.com/codesearch?hl=en&lr=&q="prototype+%3D+[]%3B"+lang%3Ajavascript&sbtn=Search, www.google.com/codesearch?hl=en&lr=&q="prototype+%3D+new+Array%3B"+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 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: esdiscuss/2010-February/010830, esdiscuss/2010-February/010832

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 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 to binding to be of any real use.

# Brendan Eich (14 years ago)

On Apr 29, 2010, at 11:44 AM, Garrett Smith wrote:

On Thu, Apr 29, 2010 at 3:01 AM, Alex Russell <alex at dojotoolkit.org>
wrote:

From 15.3.4.5:

  1. Let F be a new native ECMAScript object .

I can't discern any particular reason why equivalence should succeed for multiple calls to bind() from the spec. What am I missing?

The bound function that is returned can be saved as a property of something, so that it can be later removed.

var f = obj.method.bind(obj); node.addEventListener("click", f, false); node.removeEventListener("click", f, false);

Sure, you can save the bound method reference. It should remain a
first-class function -- this is in contrast to languages with only
methods on classes and no way to extract them as funargs to pass
around, rather than invoke (even .apply wants an extracted funarg).

This raises a point I mentioned in esdiscuss/2010-February/010830 et seq., namely that bound methods are not enough to avoid a million
moveTo methods if you have a million Sprite object instances, each
method bound to its instance as receiver (|this|):

let sprites = []; for (let i = 0; i < 1e6; i++) sprites[i] = new Sprite(i);

If Sprite is written conventionally:

function Sprite(i) { let sprite = {index: i, ...}; sprite.moveTo = (function (x,y) {...}).bind(sprite); return sprite; }

then implementations burn a lot of unjoined function objects just to
associate |this| with each sprite in sprites[i].moveTo(x, y).

Classical OOP languages can avoid this in the call-expression use case
(where no funarg is extracted). Even ignoring static typing or type
inference, if the callee Reference base is the object from which the
method identified by the Reference propertyName was extracted, then no
new function object need be allocated to associate that receiver with
that method (ignoring arguments.callee and other identity-leaking
hazards in JS).

How could the callee type be wrong? In JS, it could be due to funarg
extraction:

let f = s.moveTo; ...; f(x, y)

will bind |this| to the global object (non-strict) or undefined
(strict).

let o = {moveTo: s.moveTo}; ... o.moveTo(x, y)

or

s.moveTo.call(o, x, y)

likewise would try to bind |this| to o when activating moveTo. Any
scheme to prevent these receiver override attempts requires allocating
a function object that carries s along with moveTo at the point of
method extraction, if not earlier in the lifetime of s.

Alex et al.'s ! proposal would let the user decide whether to bind
early or late, by letting one use . instead of ! at the point of
reference, not at the point of method definition. On the other hand
this proposal complicates the surface language (which do I use, "." or
"!"? asks the average programmer) and requires on the fly memoization.

So with current JS implementations, whether using the ES5 built-in
Function.prototype.bind or a bind progenitor from a JS library,
naively binding every method to each instance in a constructor forces
a new unjoined function object for every method times every instance.
This not only can cost too much, it may break a wanted method-identity
property that putting the method on a common prototype object preserves.

This is a case where new syntax at the method definition site can
help. Then if one does

function Sprite(i) {...} Sprite.prototype = { method moveTo(x,y) {...} // <- new syntax here };

the price of allocating an unjoined function object to associate
moveTo with s will be incurred, and identity diverges, only if one
extracts the method to call later, or tries to override |this| via
apply or call.

But the common case of s.moveTo(x, y) for all one million Sprite
instances pays no such price and preserves the prototype-homed method
identity.

# Brendan Eich (14 years ago)

On Apr 29, 2010, at 12:11 PM, Alex Russell wrote:

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.

Define "contours" -- do you mean # instead of λ or ƒ, or the
(separate, I agree, but problematic) completion-value is implicit
return value idea? We've discussed both here over the years, but I
admit my two straw polls at conferences didn't cover the latter.

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?

Hey, don't sweat it -- I'm not your court of last appeal. We're here
merely to discuss, so I was just noting a precedent from other
languages that has been cited recently in connection with JS (e.g., at es-lab.googlecode.com/files/dr-ses.pdf) .

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.

The mutation happening behind your back is what makes NodeLists quite
different from Arrays. I can make an array instance, keep it isolated,
and its length won't change. I believe that's not true of a NodeList
(because it's not truly isolated from the DOM whence it came). Please
correct me if I'm mistaken.

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.

You said "subtype" ;-). Me, I'm happy with any term so long as it is
well-defined, which so far your use of "subtype" is not.

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".

No problem.

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.

Ok then!

Dave Herman has recently proposed 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.

EIBTI.

  • 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.

Is # the winner in your view, over all other one-letter alternatives?

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.

We prefer orthogonal primitives that compose well, so long as the
composing doesn't drag in the big mandatory library that you rightly
decry. Usability matters too; sometimes a compound built-in is
warranted (even Mark agrees, see const f(){...} which binds f as a
const in its block, freezes the function object, and for all I know
does something else to boot! ;-).

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

This is least surprising, but see my followup about method syntax to
optimize prototype sharing and avoid a million sprite moveTo bound
method objects for a million sprites.

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 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.

It's not a complete proposal, so be gentle. Whether and how the method
would be bound is an open issue, but everyone is aware of the problem
with lack of binding.

Your solution puts the binding burden on the programmer at each point
of use, rather than at the definition. I'm arguing for the definition
(whatever the syntax) to imply bound-ness, and enable optimizations
(with identity not preserved on extraction).

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).

Yeah, but the method definer often wants all uses to behave as if
bound, and the degree of freedom your proposal adds is strictly
unwanted. As I noted in my last post, it also complicates the surface
language and increases the cognitive load on users of methods (not
definers), who are more numerous and less skilled ("what do I use, '.'
or '!'? Crock says '.'! :-P).

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.

The . operator is not ambiguous in any sense I know of. The issue is
not simple and one-sided, user-of-dot-only. Do I, the Sprite library
author, always bind? If so, how efficiently? Do you, the consumer,
have to bind out of paranoia? (No, if we give the library author the
tool to prebind.) Do you, the library user, need to choose between '.'
and '!'? I claim not.

I don't consider proposals that leave "dot" ambiguious with to binding to be of any real use.

That's a loaded formulation, given the different interests of
abstraction producers from consumers. It's also a bit confrontational,
as if I wrote "I don't consider proposals that confuse users with
another '.'-like operator to be of any real use". :-|

I did write this:

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

So, be nice.

# Mark S. Miller (14 years ago)

On Thu, Apr 29, 2010 at 12:08 PM, Mike Samuel <mikesamuel at gmail.com> wrote:

Yeah, I'm on the other side. I think that all new productions that introduce a new keyword should be restricted. We have enough semicolon insertion problems already! :)

+1. I think all new productions should be restricted whenever the lack of restriction creates either an actual semi-insertion problem, or a visual confusion about whether there may be such a problem.

Whether the infix "!" is used for binding or for asynchronous message sending, it needs to be a restricted production so that

<left-operand>
! <stuff>

is not confused with

<expression-statement>;
! <unary-operand>

Specifically, infix "!" needs to prohibit a newline to its left.

# Brendan Eich (14 years ago)

On Apr 29, 2010, at 2:25 PM, Mark S. Miller wrote:

On Thu, Apr 29, 2010 at 12:08 PM, Mike Samuel <mikesamuel at gmail.com>
wrote: Yeah, I'm on the other side. I think that all new productions that introduce a new keyword should be restricted. We have enough semicolon insertion problems already! :)

+1. I think all new productions should be restricted whenever the
lack of restriction creates either an actual semi-insertion problem,
or a visual confusion about whether there may be such a problem.

Really, methinks you guys protest too much. In particular Mike's "all
new productions that introduce a new keyword should be restricted" is
simply wrong for "let".

It is not as if we should have made "if", "else", "for", "switch",
etc. require the next token ('(' for "if", who knows what for "else")
to be on the same line. There is no ASI hazard with any of these
control-structure introducing keywords.

We're really talking about only postfix ++/--, "throw E", "return E",
"break L", and "continue L" if memory serves.

The other Harmony keyword in mind beyond "let", already reserved in
ES5 strict, and one which might indeed require a restricted
production, is "yield". If "yield" is supported as in JS1.7+ (after
Python 2.5+), you can write code like this:

js> function g() {yield; yield}

js> for (let i in g()) print(i)

undefined undefined

You do not have to write "yield undefined;". It's an edge case but a
win when you need it, and it makes yield like return when used in a
statement context (we share the recursive descent parsing code in
SpiderMonkey).

Whether the infix "!" is used for binding or for asynchronous
message sending, it needs to be a restricted production so that

<left-operand>
! <stuff>

is not confused with

<expression-statement>;
! <unary-operand>

Specifically, infix "!" needs to prohibit a newline to its left.

Indeed, although there's no strawman proposal for "!" yet.

To quote dherman: syntax, yay.

# Brendan Eich (14 years ago)

On Apr 29, 2010, at 12:47 PM, Brendan Eich wrote:

On Apr 29, 2010, at 12:11 PM, Alex Russell wrote:

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.

Define "contours" -- do you mean # instead of λ or ƒ, or the
(separate, I agree, but problematic) completion-value is implicit
return value idea? We've discussed both here over the years, but I
admit my two straw polls at conferences didn't cover the latter.

The JSConf audience poll did provoke someone to suggest "fun", and I
mentioned "fn" (the ML family languages have both). Two letters or
three might be few enough, and avoid the line-noise and can't-type- Greek issues.

# Mike Samuel (14 years ago)

2010/4/29 Brendan Eich <brendan at mozilla.com>:

On Apr 29, 2010, at 2:25 PM, Mark S. Miller wrote:

On Thu, Apr 29, 2010 at 12:08 PM, Mike Samuel <mikesamuel at gmail.com> wrote:

Yeah, I'm on the other side.  I think that all new productions that introduce a new keyword should be restricted.  We have enough semicolon insertion problems already! :)

+1. I think all new productions should be restricted whenever the lack of restriction creates either an actual semi-insertion problem, or a visual confusion about whether there may be such a problem.

Really, methinks you guys protest too much. In particular Mike's "all new

Methinks "you protest too much, methinks" does not mean what you think it means :)

productions that introduce a new keyword should be restricted" is simply wrong for "let".

It is not as if we should have made "if", "else", "for", "switch", etc. require the next token ('(' for "if", who knows what for "else") to be on the same line. There is no ASI hazard with any of these control-structure introducing keywords.

For tokens after which no semicolon can be inserted regardless of next token, I agree. For others, I would prefer to limit the spread of semicolon-insertion infection. And I would note that not restricting productions can make it harder to introduce new similar grammatical constructs later.

We're really talking about only postfix ++/--, "throw E", "return E", "break L", and "continue L" if memory serves.

And why is "throw" in that category if you're only interested in ASI hazards.

# Brendan Eich (14 years ago)

On Apr 29, 2010, at 2:50 PM, Mike Samuel wrote:

Methinks "you protest too much, methinks" does not mean what you think it means :)

Ok, whatever the case, I'm protesting the rule written the way you
wrote it :-).

For tokens after which no semicolon can be inserted regardless of next token, I agree.

Great; I was interpreting your rule as stated a bit too literally.

For others, I would prefer to limit the spread of semicolon- insertion infection. And I would note that not restricting productions can make it harder to introduce new similar grammatical constructs later.

Agreed. We're really only talking about "yield" here, I claim.

I don't know what will happen with promises, but Mark's right that "!"
could need restriction if it comes along as a binary operator. This is
not on TC39's Harmony radar yet.

We're really talking about only postfix ++/--, "throw E", "return
E", "break L", and "continue L" if memory serves.

And why is "throw" in that category if you're only interested in ASI
hazards.

Because it is in fact a restricted production (ES5 7.9.1 in the ASI
summary, and the Throw Statement clause 12.13):

Syntax

ThrowStatement : throw [no LineTerminator here] Expression ;

Why is this? There's no rationale recorded in ES3 or ES5. I was not
involved in ES3 all the way, only in the early few meetings. Waldemar
may remember. It probably is this way because similarity to "return"
means if "throw"'s production weren't restricted the same way,
mistakes changing from one to the other, or expecting the two to work
alike, would be more likely. Or it might have been future-proofing (as
you note above, not restricting makes it harder later).

# Mark S. Miller (14 years ago)

On Thu, Apr 29, 2010 at 2:39 PM, Brendan Eich <brendan at mozilla.com> wrote:

Really, methinks you guys protest too much. In particular Mike's "all new productions that introduce a new keyword should be restricted" is simply wrong for "let".

Btw, regarding "let", ASI, and Dave's recent "let" expression proposal < strawman:let_expressions>, how should

let () { => foo }
(3, 4)

parse? Is it a function call?

# Brendan Eich (14 years ago)

On Apr 29, 2010, at 3:16 PM, Mark S. Miller wrote:

On Thu, Apr 29, 2010 at 2:39 PM, Brendan Eich <brendan at mozilla.com>
wrote: Really, methinks you guys protest too much. In particular Mike's
"all new productions that introduce a new keyword should be
restricted" is simply wrong for "let".

Btw, regarding "let", ASI, and Dave's recent "let" expression
proposal <strawman:let_expressions

, how should

let () { => foo }
(3, 4)

parse? Is it a function call?

As far as I can tell, this is covered by ECMA-262 7.9.2:

The source

a = b + c (d + e).print()

is not transformed by automatic semicolon insertion, because the
parenthesised expression that begins the second line can be interpreted as an argument list for a function call:

a = b + c(d + e).print()

In the circumstance that an assignment statement must begin with a
left parenthesis, it is a good idea for the programmer to provide an explicit semicolon at the end of the
preceding statement rather than to rely on automatic semicolon insertion.

As David-Sarah Hopwood pointed out a while ago, there's no error to
correct.

I'm assuming here that let () { => foo } will be a PrimaryExpression.

The always-braced body avoids reduce-reduce conflicts Waldemar pointed
out in connection with ES4's unbracketed let expression body.

# Mike Samuel (14 years ago)

2010/4/29 Brendan Eich <brendan at mozilla.com>:

On Apr 29, 2010, at 2:50 PM, Mike Samuel wrote:

Methinks "you protest too much, methinks" does not mean what you think it means :)

Ok, whatever the case, I'm protesting the rule written the way you wrote it :-).

For tokens after which no semicolon can be inserted regardless of next token, I agree.

Great; I was interpreting your rule as stated a bit too literally.

Sorry for not stating it clearly.

And why is "throw" in that category if you're only interested in ASI hazards.

Because it is in fact a restricted production (ES5 7.9.1 in the ASI summary, and the Throw Statement clause 12.13): Syntax ThrowStatement :     throw [no LineTerminator here] Expression ;

Sorry. I know that it is. I just have never understood why. I misread your list not as a reiteration of what are restricted productions, but as a list of productions that would be problematic absent restrictions.

# Mark S. Miller (14 years ago)

On Thu, Apr 29, 2010 at 3:32 PM, Brendan Eich <brendan at mozilla.com> wrote:

On Apr 29, 2010, at 3:16 PM, Mark S. Miller wrote:

On Thu, Apr 29, 2010 at 2:39 PM, Brendan Eich <brendan at mozilla.com> wrote:

Really, methinks you guys protest too much. In particular Mike's "all new productions that introduce a new keyword should be restricted" is simply wrong for "let".

Btw, regarding "let", ASI, and Dave's recent "let" expression proposal < strawman:let_expressions>, how should

let () { => foo }
(3, 4)

parse? Is it a function call?

As far as I can tell, this is covered by ECMA-262 7.9.2:

The source * *

  • a = b + c*
  • (d + e).print()*

is *not *transformed by automatic semicolon insertion, because the parenthesised expression that begins the second line can be interpreted as an argument list for a function call: * *

  • a = b + c(d + e).print()*

Formally, that makes sense. But given that all the other

<keyword> ... "{" ... "}"

productions, if they occur at the beginning of a statement, end with the "}", I think this reading of the let expression may be confusing. What if we adopt the same (admittedly unpleasant) rule we already have for the "function" keyword: It can't begin an expression statement. If it occurs at the beginning of a statement, it is a statement. If you want a let expression in that position, surround it with parens.

In other words, better to be unpleasantly consistent than to be inconsistent. (Please no "small mind hobgoblins" ;).)

# Mark S. Miller (14 years ago)

On Thu, Apr 29, 2010 at 10:03 AM, Brendan Eich <brendan at mozilla.com> wrote:

[...]

// 2.) Shorthand for "function", aka the "not lambda"

[...]

// 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.

Dave Herman has recently proposed 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.

  • 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.

First of all, +1 for Dave's let expression syntax. The ASI issues I just raised are details. Whatever answers we arrive at for those, I'm still +1 on let expressions.

Second, +1 on "#" as a short way to define closures, whether they are simply replacing the "function" keyword or introducing a genuine lambda.

Third, given the first two, +1 on "#" reviving lambda, with Dave's same "=>"

replacing the notion of completion value within the lambda body.

I know that in the past I have advocated let expressions while agreeing to drop lambda. However, now that I've had time to sleep on introducing let expressions into the language, I think the marginal psychological complexity of introducing lambda along with let is not so great after all. And it avoids a different source of confusion. For example, without lambda, when people improve iteration code like

for (var i = 0; i < arr.length; i++) {
    // ...
    if (pred(arr[i])) { return arr[i]; }
    // ...
}

to use the new array generics, they will often first write

arr.forEach(function(el) {
    // ...
    if (pred(el)) { return el; }
    // ...
});

which won't work at all. By contrast, if "#" means lambda,

arr.forEach(#(ev) {
    // ...
    if (pred(el)) { return el; }
    // ...
});

will work just fine. The array generics really are our first non-special-form control abstractions. Control abstractions without a TCP-respecting lambda will simply be confusing. Control abstractions without a short function call syntax (whether meaning lambda or function) are simply too verbose.

Even if lambda remains rejected, I'd still prefer "#" for "function", rather than to leave the current verbosity unrepaired.

# Mark S. Miller (14 years ago)

On Thu, Apr 29, 2010 at 2:40 PM, Brendan Eich <brendan at mozilla.com> wrote:

The JSConf audience poll did provoke someone to suggest "fun", and I mentioned "fn" (the ML family languages have both). Two letters or three might be few enough, and avoid the line-noise and can't-type-Greek issues.

Neither "fun" nor "fn" are reserved identifiers. They are short ascii identifiers, so conflicts are virtually guaranteed. I don't see any realistic way to make them keywords without either breaking the web or (in your terminology) raising the opt-in migration tax too high. Am I missing something? If there is a painless way to introduce short keywords that weren't previously reserved, I'd love to understand that.

# Mike Samuel (14 years ago)

2010/4/29 Mark S. Miller <erights at google.com>:

On Thu, Apr 29, 2010 at 2:40 PM, Brendan Eich <brendan at mozilla.com> wrote:

The JSConf audience poll did provoke someone to suggest "fun", and I mentioned "fn" (the ML family languages have both). Two letters or three might be few enough, and avoid the line-noise and can't-type-Greek issues.

Neither "fun" nor "fn" are reserved identifiers. They are short ascii identifiers, so conflicts are virtually guaranteed. I don't see any realistic way to make them keywords without either breaking the web or (in your terminology) raising the opt-in migration tax too high. Am I missing something? If there is a painless way to introduce short keywords that weren't previously reserved, I'd love to understand that.

www.google.com/codesearch?q=file:.js$+\bfun\b, www.google.com/codesearch?q=file:.js$+\bfn\b

fun is used as an identifier in dojo in the first hit, and fn is used as an identifier in jquery in the first hit.

# Brendan Eich (14 years ago)

On Apr 29, 2010, at 4:33 PM, Mark S. Miller wrote:

On Thu, Apr 29, 2010 at 2:40 PM, Brendan Eich <brendan at mozilla.com>
wrote: The JSConf audience poll did provoke someone to suggest "fun", and I
mentioned "fn" (the ML family languages have both). Two letters or
three might be few enough, and avoid the line-noise and can't-type- Greek issues.

Neither "fun" nor "fn" are reserved identifiers. They are short
ascii identifiers, so conflicts are virtually guaranteed. I don't
see any realistic way to make them keywords without either breaking
the web or (in your terminology) raising the opt-in migration tax
too high. Am I missing something?

No, you're not missing anything, except possibly this: the same
objection applies to lambda (λ) and florin (ƒ).

On the other hand, with opt-in versioning, developers might find that
the migration tax is not that bad, for any of these including "fn" and
perhaps even "fun".

If there is a painless way to introduce short keywords that weren't
previously reserved, I'd love to understand that.

It's all about the low odds of ƒ or (I have to go copy and paste now)
λ or fn being used as an identifier. The odds are worse for fn, even
worse for fun, but possibly still low enough.

But again, any Unicode identifier however short (if not already
reserved) is an incompatible change from ES1-5.

# Brendan Eich (14 years ago)

On Apr 29, 2010, at 4:39 PM, Mike Samuel wrote:

2010/4/29 Mark S. Miller <erights at google.com>:

On Thu, Apr 29, 2010 at 2:40 PM, Brendan Eich <brendan at mozilla.com>
wrote:

The JSConf audience poll did provoke someone to suggest "fun", and I mentioned "fn" (the ML family languages have both). Two letters or
three might be few enough, and avoid the line-noise and can't-type-Greek
issues.

Neither "fun" nor "fn" are reserved identifiers. They are short ascii identifiers, so conflicts are virtually guaranteed. I don't see any realistic way to make them keywords without either breaking the web
or (in your terminology) raising the opt-in migration tax too high. Am I
missing something? If there is a painless way to introduce short keywords
that weren't previously reserved, I'd love to understand that.

www.google.com/codesearch?q=file:.js$+\bfun\b, www.google.com/codesearch?q=file:.js$+\bfn\b

fun is used as an identifier in dojo in the first hit, and fn is used as an identifier in jquery in the first hit.

Yeah; the .fn (fn as qualified property name) uses are ok, of course.
ES5 unreserves keywords in property name contexts (after. and before :
in an object initialiser).

Without getting all PL/I here, we're probably out of luck on fn and
fun. The temptation is to allow expressions other than at the start of
statements to begin with these, provided they are followed by an
identifier or a ( -- but in the latter case we would have to rule out
a function denoted fn being called. This is the road to hell.

At JSConf I said in reply to the person who suggested "fun" that it's
hard to reserve new identifiers. But we're trying with let and yield,
not to mention const (not reserved by IE, AFAIK). Some browsers
reserve const already, so perhaps that helps. No one reserves fun or
fn, and your codesearch shows people colonizing those two fine plots
in the space of all names.

So, back to the funny letters and #. ;-)

# Brendan Eich (14 years ago)

On Apr 29, 2010, at 3:48 PM, Mark S. Miller wrote:

On Thu, Apr 29, 2010 at 3:32 PM, Brendan Eich <brendan at mozilla.com>
wrote:As far as I can tell, this is covered by ECMA-262 7.9.2:

The source

a = b + c (d + e).print()

is not transformed by automatic semicolon insertion, because the
parenthesised expression that begins the second line can be interpreted as an argument list for a function call:

a = b + c(d + e).print()

Formally, that makes sense. But given that all the other

<keyword> ... "{" ... "}"

productions, if they occur at the beginning of a statement, end with
the "}", I think this reading of the let expression may be
confusing. What if we adopt the same (admittedly unpleasant) rule we
already have for the "function" keyword: It can't begin an
expression statement. If it occurs at the beginning of a statement,
it is a statement. If you want a let expression in that position,
surround it with parens.

Yeah, we can do this -- it will not break us since we do it for
function and { already. In particular, you can't start an object
initialiser at the front of a statement -- this bites destructuring
assignment with an object pattern too, but in practice it's ok:
majority case uses a binding keyword to destructure to fresh names;
minority case, you parenthesize.

Thanks for pointing this out.

# Brendan Eich (14 years ago)

On Apr 29, 2010, at 4:18 PM, Mark S. Miller wrote:

Second, +1 on "#" as a short way to define closures, whether they
are simply replacing the "function" keyword or introducing a genuine
lambda.

The ƒ fans are still strong, so we'll have to run a txt-your-vote
dancing with the stars elimination round ;-)>

Third, given the first two, +1 on "#" reviving lambda, with Dave's
same "=>" replacing the notion of completion value within the lambda
body.

This goes beyond what we've agreed to, though -- TCP making return in
your example return from the outer function (which you didn't show
fully):

arr.forEach(#(ev) {
    // ...
    if (pred(el)) { return el; }
    // ...
});

I'm not sure everyone on es-discuss groks this. You're supposing the
above is in a function, and the return el forces control flow to
return from that outer function. Full example:

function findElementSatisfyingPredicateWhileDoingOtherStuff(arr, pred) { arr.forEach(#(el) { // ... if (pred(el)) return el; // ... }); return null; }

We discussed this on the list. Maciej argued strongly against TCP as
maximum good that trumps other considerations:

esdiscuss/2008-December/008390

Quoting his words:

"So return from a lambda is sometimes but not always a runtime error? Other times it can return through multiple levels of function calls without raising an exception? That seems pretty bad for ease of understanding and for performance of implementations. If you do this: [1 2 3].map(lambda (x) { return x + 1; }) I think it would be better for that to be a syntax error than to make the containing function return 2. It seems to me the current design prioritizes lambda as a desugaring construct or building block for imperative-style control flow, over use as an actual first-class function. I assume break and continue inside a lambda have similar issues." --- end quote ---

Then you replied: esdiscuss/2008-December/008391 And Allen agreed: esdiscuss/2008-December/008392

Quoting Allen: "In which case perhaps we should abandon this style of
lambda and work harder at developing a concise construct that is a
reformed function."

The explicit completion value here-syntax in Dave's let expression
proposal does not address all the concerns from this end-of-2008
lambda thread. We can't keep going around on this. I'm all in favor of
shorthand for function, but TC39 virtually dropped lambda. Do we
really need to revive it (and return to label, and probably other
things we probably can't afford)?

# Mark S. Miller (14 years ago)

On Thu, Apr 29, 2010 at 6:11 PM, Brendan Eich <brendan at mozilla.com> wrote:

We discussed this on the list. Maciej argued strongly against TCP as maximum good that trumps other considerations: esdiscuss/2008-December/008390 [...] Then you replied: esdiscuss/2008-December/008391 And Allen agreed: esdiscuss/2008-December/008392 [...] The explicit completion value here-syntax in Dave's let expression proposal does not address all the concerns from this end-of-2008 lambda thread.

No, but Dave's syntax does address one of the main reasons I gave up on lambda. The prospect of introducing it anyway to support the let expression makes lambda seem like a much lighter weight concept to me.

We can't keep going around on this. I'm all in favor of shorthand for function, but TC39 virtually dropped lambda. Do we really need to revive it (and return to label, and probably other things we probably can't afford)?

What's the metric of "afford" here? If these things impose a surprising implementation cost, then I'm all ears. Do they?

In any case, lambda aside, I do think we need at least break and continue to unshadowed labels in lexically enclosing functions. Since labels are already a separate namespace, and since a break or continue to a non-local label is currently a static error, generalizing labels to follow inter-procedural static scope visibility would be fully upwards compatible with zero new syntax. It would make the language psychologically simpler by removing the need to explain an irregular restriction.

Given these, I'd also like a return to label if a nice syntax can be found. I don't like the currently proposed syntax, but have nothing better to suggest.

# Douglas Crockford (14 years ago)

On 11:59 AM, Brendan Eich wrote:

On Apr 29, 2010, at 4:33 PM, Mark S. Miller wrote:

On Thu, Apr 29, 2010 at 2:40 PM, Brendan Eich <brendan at mozilla.com <mailto:brendan at mozilla.com>> wrote:

The JSConf audience poll did provoke someone to suggest "fun",
and I mentioned "fn" (the ML family languages have both). Two
letters or three might be few enough, and avoid the line-noise
and can't-type-Greek issues.

Neither "fun" nor "fn" are reserved identifiers. They are short ascii identifiers, so conflicts are virtually guaranteed. I don't see any realistic way to make them keywords without either breaking the web or (in your terminology) raising the opt-in migration tax too high. Am I missing something?

No, you're not missing anything, except possibly this: the same objection applies to lambda (λ) and florin (ƒ).

On the other hand, with opt-in versioning, developers might find that the migration tax is not that bad, for any of these including "fn" and perhaps even "fun".

If there is a painless way to introduce short keywords that weren't previously reserved, I'd love to understand that.

It's all about the low odds of ƒ or (I have to go copy and paste now) λ or fn being used as an identifier. The odds are worse for fn, even worse for fun, but possibly still low enough.

But again, any Unicode identifier however short (if not already reserved) is an incompatible change from ES1-5.

/be

I think we should be focused on solving real problems. We have some significant problems to solve, and 'function' being 8 letters isn't one of them.

# Alex Russell (14 years ago)

On Apr 29, 2010, at 7:55 PM, Douglas Crockford wrote:

On 11:59 AM, Brendan Eich wrote:

On Apr 29, 2010, at 4:33 PM, Mark S. Miller wrote:

On Thu, Apr 29, 2010 at 2:40 PM, Brendan Eich <brendan at mozilla.com
<mailto:brendan at mozilla.com>> wrote:

The JSConf audience poll did provoke someone to suggest "fun", and I mentioned "fn" (the ML family languages have both). Two letters or three might be few enough, and avoid the line-noise and can't-type-Greek issues.

Neither "fun" nor "fn" are reserved identifiers. They are short
ascii identifiers, so conflicts are virtually guaranteed. I don't
see any realistic way to make them keywords without either
breaking the web or (in your terminology) raising the opt-in
migration tax too high. Am I missing something?

No, you're not missing anything, except possibly this: the same
objection applies to lambda (λ) and florin (ƒ).

On the other hand, with opt-in versioning, developers might find
that the migration tax is not that bad, for any of these including
"fn" and perhaps even "fun".

If there is a painless way to introduce short keywords that
weren't previously reserved, I'd love to understand that.

It's all about the low odds of ƒ or (I have to go copy and paste
now) λ or fn being used as an identifier. The odds are worse for
fn, even worse for fun, but possibly still low enough.

But again, any Unicode identifier however short (if not already
reserved) is an incompatible change from ES1-5.

/be

I think we should be focused on solving real problems. We have some
significant problems to solve, and 'function' being 8 letters isn't
one of them.

I'll take that as a rubber-stamp +1 then ;-)

# Brendan Eich (14 years ago)

On Apr 29, 2010, at 7:26 PM, Mark S. Miller wrote:

We can't keep going around on this. I'm all in favor of shorthand
for function, but TC39 virtually dropped lambda. Do we really need
to revive it (and return to label, and probably other things we
probably can't afford)?

What's the metric of "afford" here? If these things impose a surprising implementation cost, then I'm all ears. Do they?

Maciej's cited objection was not about implementation costs. It was
about what users will expect. If lambda is seen as a shorthand for
function, then return in lambda will be read as return from lambda.

There's also the surprising runtime error you get when you return from
a lambda that has escaped and outlived the activation of the nearest
enclosing function.

Implementation complexity goes up too, but the user confusion issue is
primary.

In any case, lambda aside, I do think we need at least break and continue to unshadowed labels in lexically enclosing functions.

This has the same dynamic error problem in case of inner function
escape.

I've never heard any request break or continue to a label in an
outer function, btw.

# Maciej Stachowiak (14 years ago)

On Apr 29, 2010, at 9:09 PM, Brendan Eich wrote:

On Apr 29, 2010, at 7:26 PM, Mark S. Miller wrote:

We can't keep going around on this. I'm all in favor of shorthand
for function, but TC39 virtually dropped lambda. Do we really need
to revive it (and return to label, and probably other things we
probably can't afford)?

What's the metric of "afford" here? If these things impose a surprising implementation cost, then I'm all ears. Do they?

Maciej's cited objection was not about implementation costs. It was
about what users will expect. If lambda is seen as a shorthand for
function, then return in lambda will be read as return from lambda.

There's also the surprising runtime error you get when you return
from a lambda that has escaped and outlived the activation of the
nearest enclosing function.

Implementation complexity goes up too, but the user confusion issue
is primary.

I like the idea of a shorter syntax for functions. However, I don't
think ES4 lambda's goal of referential transparency results in a
useful construct. A syntax for creating a function where "return"
exits the containing function breaks the programmer's concept of a
function, even if it may have some useful formal properties.

In any case, lambda aside, I do think we need at least break and continue to unshadowed labels in lexically enclosing functions.

This has the same dynamic error problem in case of inner function
escape.

I've never heard any request break or continue to a label in an
outer function, btw.

break, continue or return being affected by the containing function
(and whether it is currently on the call stack) just isn't a good fit
for a language with first-class functions. Closures should capture the
lexical environment, not the call stack. Control flow constructs based
on the call stack are really just syntactic sugar for exceptions, and
we already have try-catch.

, Maciej

# Brendan Eich (14 years ago)

On May 1, 2010, at 1:47 PM, Maciej Stachowiak wrote:

On Apr 29, 2010, at 9:09 PM, Brendan Eich wrote:

On Apr 29, 2010, at 7:26 PM, Mark S. Miller wrote:

We can't keep going around on this. I'm all in favor of shorthand
for function, but TC39 virtually dropped lambda. Do we really
need to revive it (and return to label, and probably other things
we probably can't afford)?

What's the metric of "afford" here? If these things impose a surprising implementation cost, then I'm all ears. Do they?

Maciej's cited objection was not about implementation costs. It was
about what users will expect. If lambda is seen as a shorthand for
function, then return in lambda will be read as return from lambda.

There's also the surprising runtime error you get when you return
from a lambda that has escaped and outlived the activation of the
nearest enclosing function.

Implementation complexity goes up too, but the user confusion issue
is primary.

I like the idea of a shorter syntax for functions. However, I don't
think ES4 lambda's goal of referential transparency results in a
useful construct. A syntax for creating a function where "return"
exits the containing function breaks the programmer's concept of a
function, even if it may have some useful formal properties.

Agreed, and this seemed consensus almost 1.5 years ago, on the list
and among TC39 members who spoke up.

In any case, lambda aside, I do think we need at least break and continue to unshadowed labels in lexically enclosing functions.

This has the same dynamic error problem in case of inner function
escape.

I've never heard any request break or continue to a label in an
outer function, btw.

break, continue or return being affected by the containing function
(and whether it is currently on the call stack) just isn't a good
fit for a language with first-class functions. Closures should
capture the lexical environment, not the call stack. Control flow
constructs based on the call stack are really just syntactic sugar
for exceptions, and we already have try-catch.

Agreed, again.

# Jürg Lehni (14 years ago)

To pick up the lambda discussion again, it is maybe of interest to have an eye on the recent developments around the introductions of Lambda in Java:

www.infoq.com/news/2010/06/lambda-syntax-debate, stronglytypedblog.blogspot.com/2010/06/so-you-find-java-7-closure-syntax-nasty.html

Jürg

# Jeff Watkins (14 years ago)

I would think we'd use the ghastly syntax of Java as a cautionary tale rather than something from which to draw inspiration...

# Jürg Lehni (14 years ago)

That's exactly why I pointed it out, apologies for not making that clearer.