Expression Closures as Compliment to Arrow Functions
On Mar 23, 2015, at 10:42 AM, Jacob Parker <jacobparker1992 at gmail.com <mailto:jacobparker1992 at gmail.com>> wrote:
I noticed expression closures, as defined below, have been excluded from the spec.
developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Expression_closures, developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Expression_closures
Currently implemented (and deprecated) in Firefox, which hasn't broken anything by the looks of things.
While offering little above arrow functions,
exactly
including these, in addition to the existing shorthand syntaxes, should make the following examples work.
var x = { value: 3, toString() 'string', valueOf() this.value }; class x { constructor { this.value = 3; } valueOf() this.value }
what you show above would be an expression bodied "concise method" people.mozilla.org/~jorendorff/es6-draft.html#sec-method-definitions, people.mozilla.org/~jorendorff/es6-draft.html#sec-method-definitions That’s a different beast from a FF “expression closure”.
These were consider but not adopted for ES6. Primarily because they introduced grammar, ASI and/or other issues that at the time didn’t see worth trying to solve.
Allen Wirfs-Brock wrote:
including these, in addition to the existing shorthand syntaxes, should make the following examples work.
var x = { value: 3, toString() 'string', valueOf() this.value }; class x { constructor { this.value = 3; } valueOf() this.value }
what you show above would be an expression bodied "concise method" people.mozilla.org/~jorendorff/es6-draft.html#sec-method-definitions, people.mozilla.org/~jorendorff/es6-draft.html#sec-method-definitions That’s a different beast from a FF “expression closure”.
These were consider but not adopted for ES6. Primarily because they introduced grammar, ASI and/or other issues that at the time didn’t see worth trying to solve.
To be precise, the full function (args) expr
form inverts precedence,
creating fatal ambiguity. Here's an example from the old thread on an
equvalent problem that afflicted ES4's let expressions:
function f() {return "f"} var x = 3; var y = function (a) a ? f : x++(1); print(y);
This should print "f" but it instead prints function f converted to a string.
Expression closures are dead, IMHO. Even though the variant in object literals could be supported.
Either I'm wrong, or that's missing some parens. Assume the following (the only interpretation I can see to not throw a syntax error),
var y = (function (a) a ? f : x++)(1);
In which case, that could only return f
, which when printed should print
the source of f, no?
FWIW, the consise body of arrow functions won't allow the original format, and must have surrounding parens. Would just allowing consise bodies on functions work?
I only push for the consise body on functions, as it's easier to understand
why when using this format in object literals, this
would then not be
lexical, as is the case for arrow functions.
Jacob Parker schrieb:
Either I'm wrong, or that's missing some parens. Assume the following (the only interpretation I can see to not throw a syntax error),
var y = (function (a) a ? f : x++)(1);
In which case, that could only return
f
, which when printed should print the source of f, no?
Uh, I had some problems with that either, but finally got it now. If we make the parens explicit, the point is that the expression should be evaluated as
var y = ((function (a) a) ? f : x++)(1); // === f(1)
instead of
var y = (function (a) (a ? f : x++))(1); // === f
Admittedly, I think arrow functions have same problem. How is
var y = (a) => a ? f : x++ (1);
evaluated (or is it syntactically valid at all)?
Bergi
Bergi wrote:
Jacob Parker schrieb:
Either I'm wrong, or that's missing some parens. Assume the following (the only interpretation I can see to not throw a syntax error),
var y = (function (a) a ? f : x++)(1);
In which case, that could only return
f
, which when printed should print the source of f, no?Uh, I had some problems with that either, but finally got it now. If we make the parens explicit, the point is that the expression should be evaluated as
var y = ((function (a) a) ? f : x++)(1); // === f(1)
instead of
var y = (function (a) (a ? f : x++))(1); // === f
That's right.
Admittedly, I think arrow functions have same problem. How is
var y = (a) => a ? f : x++ (1);
evaluated (or is it syntactically valid at all)?
The problem with expression closures is precedence inversion: you have LowPrec ~~> HighPrec ~~> stuff ending with LowPrec. The particular
nonterminals are AssignmentExpression and PrimaryExpression.
No such problem afflicts arrows, because they are low-precedence, in fact AssignmentExpression alternative right-hand sides. So your example here is a syntax error, because you are placing two assignment expressions next to one another with no operator or semicolon in between.
Jacob Parker wrote:
Either I'm wrong, or that's missing some parens.
No, test it yourself in Firefox (s/print/console.log/) or SpiderMonkey.
Assume the following (the only interpretation I can see to not throw a syntax error),
var y = (function (a) a ? f : x++)(1);
In which case, that could only return
f
, which when printed should print the source of f, no?
No. :-P
But seriously, I'm not sure what your last line means. The mis-parsing due to precedence inversion means SpiderMonkey (in Firefox) returns f, not "f", as the value that initializes y, given the minimally parenthesized testcase:
function f() {return "f"} var x = 3; var y = function (a) a ? f : x++(1); print(y);
FWIW, the consise body of arrow functions won't allow the original format, and must have surrounding parens.
Yes, arrows are low-precedence (AssignmentExpression alternatives), so they don't suffer from precedence inversion.
Would just allowing consise bodies on functions work?
No, because function expressions are high-precedence (PrimaryExpression).
Brendan Eich wrote:
var y = (a) => a ? f : x++ (1);
evaluated (or is it syntactically valid at all)?
The problem with expression closures is precedence inversion: you have LowPrec ~~> HighPrec ~~> stuff ending with LowPrec. The particular nonterminals are AssignmentExpression and PrimaryExpression.
No such problem afflicts arrows, because they are low-precedence, in fact AssignmentExpression alternative right-hand sides. So your example here is a syntax error, because you are placing two assignment expressions
I mean an assignment expression followed by a primary expression, of
course. The (a) => a ? f : x++
arrow is an assignment expression, the
(1)
is a separate expression. The (1)
cannot be the actual parameter
list for the arrow unless you parenthesize the arrow to make it be a
MemberExpression (the highest precedence non-terminal that can be the
callee in a CallExpression).
Precedence, whee!
In the context of only objects and classes, is this format no-go?
Jacob Parker wrote:
In the context of only objects and classes, is this format no-go?
Without the } that closes a concise method body, there's a new problem to-do with computed property names:
class C { m() this._m Symbol.iterator {/.../} }
We need a delimiter. Could use ; without ASI, so it'd look like this:
class C { m() this._m; Symbol.iterator {/.../} }
I haven't checked for other problems, but wanted to throw this out and see if anyone else sees a live one.
Could the comma not be the delimiter, as I think works with arrow functions, or is that more precedence issues?
Jacob Parker wrote:
Could the comma not be the delimiter, as I think works with arrow functions, or is that more precedence issues?
No precedence issue, due to AssignmentExpression (not Expression) being the right-most non-terminal produced by the unbraced alternative for ConciseBody.
In a class body, comma is the wrong separator (delimiter? trailing comma allowed in most JS contexts, but still) -- see the live ES7 proposal from Jeff Morrison, inspired by TypeScript and Flow:
gist.github.com/jeffmo/054df782c05639da2adb
Class bodies are not object initialisers, in notable ways, even though
concise methods have the same syntax and (parameterizing super
differently according to context) semantics.
Class bodies are not block statements, either, of course. But this doesn't rule out ; or favor , instead. It just means no ASI insanity. ;-)
Would this be rectifiable with something like an unbound lambda type syntax? You could even make an analogous equivalent for classes.
// Lambda, unbound, ASI applies, same precedence as current arrow functions
() -> foo;
(x) -> bar(1, x);
x -> bar(1, x); // same as previous
(...args) -> foo(this, ...args);
() -> {
try {
foo();
return true;
} catch (e) {
return false;
}
};
// Useful for jQuery, etc.
$('.foo').each(() -> $(this).hide());
// For classes
class Foo {
// shorthand method, only allowed
// in ClassBody to limit ambiguity
bar () -> this.foo.toLowerCase();
get chars () -> this.bar().split("");
constructor(foo="") {
this.foo = foo;
}
}
// Prototypal
const Foo = {
bar: () -> this.foo.toLowerCase(),
get chars () -> this.bar().split(""),
init: (foo="") -> this.foo = foo,
};
// let foo1 = Object.create(Foo);
// foo1.init();
let foo1 = new Foo();
// let foo2 = Object.create(Foo);
// foo2.init("Foo!");
let foo2 = new Foo("Foo!");
foo1.bar(); // ""
foo2.bar(); // "foo!"
foo1.chars; // []
foo2.chars; // ["f", "o", "o", "!"]
I think an unbound lambda like that above would be the best, most flexible solution. Obviously, some inspiration is taken from CoffeeScript, but it works.
Also, the syntax would be identical to that of arrow functions, except substitute the arrow type, "=>" to "->".
Now, as for potential syntax ambiguities, there shouldn't exist any.
- "n-->1" vs "n->1": The character after the first hyphen will decide how
it's parsed. If it's ">", then it represents an unbound arrow function.
- "foo () {}" vs "foo () -> this.bar": If the first character after the
arguments list is an opening curly brace, then it is a concise method. If it's a hyphen, it's an unbound arrow lambda definition. Same logic for getters/setters.
- "static foo () {}" vs "static foo () -> bar" vs "static foo () => bar":
see above
These unbound lambdas would be terminated just like arrow functions, and in classes, the ASI is after the expression statement or block statement.
Basically, the grammar would be otherwise identical to arrow functions, except for both types can substitute for static method arguments + body and unbound for instance method arguments + body.
Any interest in this solution? Also, WDYT?
class Foo {
// This syntax is only allowed in
// a ClassBody
foo () -> this;
foo2 x -> f(this, x);
foo3 (x, y) -> f(this, x, y);
get bar () -> this._bar;
set bar val -> this._bar = 2;
static baz () => blarg();
static get quux () => foo();
static set quux val => foo(val);
}
$(".foo").each(() -> $(this).hide());
list.forEach(x => x * 2);
let f = x => x + 1;
let g = () -> h(this);
From: Brendan Eich <brendan at mozilla.org> To: Jacob Parker <jacobparker1992 at gmail.com> Cc: es-discuss at mozilla.org Date: Wed, 25 Mar 2015 14:30:28 +0100 Subject: Re: Expression Closures as Compliment to Arrow Functions Jacob Parker wrote:
Could the comma not be the delimiter, as I think works with arrow
functions, or is that more precedence issues?
No precedence issue, due to AssignmentExpression (not Expression) being
the right-most non-terminal produced by the unbraced alternative for ConciseBody.
In a class body, comma is the wrong separator (delimiter? trailing comma
allowed in most JS contexts, but still) -- see the live ES7 proposal from Jeff Morrison, inspired by TypeScript and Flow:
gist.github.com/jeffmo/054df782c05639da2adb
Class bodies are not object initialisers, in notable ways, even though
concise methods have the same syntax and (parameterizing super
differently according to context) semantics.
Class bodies are not block statements, either, of course. But this
doesn't rule out ; or favor , instead. It just means no ASI insanity. ;-)
/be
On Wed, 25 Mar 2015 8:37 am Brendan Eich <brendan at mozilla.org <mailto:
brendan at mozilla.org>> wrote:
Jacob Parker wrote: > In the context of only objects and classes, is this format no-go? Without the } that closes a concise method body, there's a new
problem
to-do with computed property names: class C { m() this._m [Symbol.iterator]() {/*...*/} } We need a delimiter. Could use ; without ASI, so it'd look like this: class C { m() this._m; [Symbol.iterator]() {/*...*/} } I haven't checked for other problems, but wanted to throw this out
and
Isiah Meadows wrote:
Would this be rectifiable with something like an unbound lambda type syntax?
There's nothing to rectify -- could do m() expr; in classes as shorter form of m() { return expr; }, and similarly in object literals (, as separator, not ; as terminator).
What gates this concise body for method shorthand idea is strong sense it's worth the added complexity (syntax should be added only with clear and strong signal that it's needed, e.g. to provide a special form for new semantics that cannot be provided by an API). And then someone to do the work drafting an ES7 proposal.
What stopped thin-arrow function syntax when I got fat-arrow through TC39 was the idea that two arrows was a bridge too far. That sense might change with time user demand, but it's not something to rush. Anyway, thin arrow is not related to this thread.
You could even make an analogous equivalent for classes.
Sure, but why require thin arrow for method shorthand, when method
requires unbound this
? Allowing fat arrow seems a hazard with no real
use case motivating it. In any case, grammatically we need no arrow at
all. There's no grammar issues AFAICS with concise methods of the m()
expr; form, provided expr is parsed as an AssignmentExpression and ; is
required.
I noticed expression closures, as defined below, have been excluded from the spec.
developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Expression_closures
Currently implemented (and deprecated) in Firefox, which hasn't broken anything by the looks of things.
While offering little above arrow functions, including these, in addition to the existing shorthand syntaxes, should make the following examples work.
Was there reasoning to not include them?