Expression Closures as Compliment to Arrow Functions

# Jacob Parker (9 years ago)

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.

var x = { value: 3, toString() 'string', valueOf() this.value };

class x { constructor { this.value = 3; } valueOf() this.value }

Was there reasoning to not include them?

# Allen Wirfs-Brock (9 years ago)

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.

# Brendan Eich (9 years ago)

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.

# Jacob Parker (9 years ago)

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.

# Bergi (9 years ago)

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

# Brendan Eich (9 years ago)

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.

# Brendan Eich (9 years ago)

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 (9 years ago)

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!

# Jacob Parker (9 years ago)

In the context of only objects and classes, is this format no-go?

# Brendan Eich (9 years ago)

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.

# Jacob Parker (9 years ago)

Could the comma not be the delimiter, as I think works with arrow functions, or is that more precedence issues?

# Brendan Eich (9 years ago)

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

# Isiah Meadows (9 years ago)

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

# Brendan Eich (9 years ago)

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.