A closer look at expression closures
I was recently working hacking on something in Firebug console and remembered about expression closures — a language addition, introduced in Mozilla’s JavaScript 1.8. The concise explanation on MDC captures the essence of expression closures:
This addition is nothing more than a shorthand for writing simple functions, giving the language something similar to a typical Lambda notation. […] This syntax allows you to leave off the braces and ‘return’ statement – making them implicit. There is no added benefit to writing code in this manner, other than having it be syntactically shorter.
And that’s exactly what I used it for — to quickly experiment with something, shortening function expressions to save on typing.
MDC also gives a simple example, showcasing a function written using expression closure:
// Regular syntax
function() { return x * x }
// Expression closure
function(x) x * x
As you can see, expression closure is essentially a more concise way to create a function. Based on MDC, expression closure allows us to drop “return” and braces denoting function body.
The information on MDC, however, was so tiny that I became curious about some of the finer details of how expression closures (EC from now on) work. Can they only be used to create function expressions? How about function declarations? What about named function expressions? Is function() ... always identical to function(){return ...} no matter what’s in the ... (function body)? And what are these “simple functions” MDC is talking about?
There was no answer to any of these questions, so I decided to quickly investigate this funky language addition a bit deeper. These are some of my findings; you’ll see that certain behavior isn’t always obvious.
Letting the code speaks for itself, here’s is a list of questions followed by simple tests, attempting to answer those questions. Feel free to skip to the next section for a shorter overview of these findings.
Tests
-
Can EC be used to create function declarations (not just function expressions)?
Yes.
function f(x, y) x + y typeof f; // "function" -
What about named function expressions?
Works as expected.
typeof (function f(x, y) x + y); // "function" -
How does EC affect function representation?
As usual, but does not include curly braces (or “return” keyword)!
String(function (x) x + x ); // "function (x) x + x;" -
What about non-standard `name` property?
Works as expected.
function foo(x, y) x + y; foo.name; // "foo" -
How about immediately invoked function expression?
Works fine.
(function(x, y) x + y)(1, 2); // 3 -
What about immediately invoked function expression without wrapping parens?
Not so fast!
var f1 = (function(x, y) x + y)(1, 2); var f2 = function(x, y) x + y (1, 2); f1; // 3 f2; // function(x, y) x + y(1, 2)As you can see,
(1, 2)followingx + yactually applies to an inner expression, so instead of calling outer function — and assigning returned value tof2—f2is being assigned a function that hasx + y(1, 2)as its body. Something to be aware of. -
How does EC work with ES5 accessor syntax (let’s try getter first)?
Works!
var obj = ({ get foo() 1 }); obj.foo; // 1 -
How about setter?
Works!
var _x; var obj = ({ set foo(x) _x = x }); obj.foo = 5; _x; // 5 -
How about both?
Works as well! Well, why wouldn’t it…
var _x = 'initial value'; var obj = ({ get foo() _x, set foo(x) _x = x }); obj.foo; // 'initial value' obj.foo = 'overwritten value'; obj.foo; // 'overwritten value' -
Can we confuse parser with a comma?
Yes we can.
({ set foo(x) bar = 1, baz = 2 }); // SyntaxErrorIn this case, we might want
bar = 1, baz = 2as an expression inside a function, but instead, comma is parsed as part of an object intializer. Wrapping expression in parenthesis would solve this problem, of course.({ set foo(x) (bar = 1, baz = 2) }); -
Are statements allowed in function body of a function created via EC?
No.
var f = function() if (true) "foo"; // ErrorThis is pretty understandable, considering that expression after
function()in EC is treated as an expression insidereturnstatement. Andreturnstatement obviously can’t contain other statements (only expressions). Fortunately, there’s always a ternary operator to take care of cases like this:var f = function() someCondition ? "foo" : "bar"; -
Can body of a function created via EC be omitted?
Nope.
function f() // ErrorYou would think that
function f()should be identical tofunction f(){ return }, but no, empty function body is not allowed there. It’s not clear why this was made like this. Parsing complexities, perhaps? -
What happens when we include more than 1 expression?
Only first one is consumed.
function f() alert("1st statement"); alert("2nd statement"); // alerts "2nd statement" -
What about automatic semicolon insertion rules?
Same rules seem to apply.
function foo(x) x (function() { /* ... */ })() foo; // function foo(x) x(function(){ /* ... */})()A classic example of ASI — function declaration followed by newline and another expression makes for an unexpected behavior. Same rules apply here, and
foofunction — even though expressed via EC — ends up having function body ofx(function(){})()(rather than justx). -
Is “{” interpreted as a block or an object literal?
As a block! Overall production essentially matches regular function syntax.
function foo(x) { top: 0, left: 0 } // SyntaxErrorYou would think that
function() { … }is interpreted asfunction(){ return { … } }but the production rules of “regular” function syntax precede here, andfunction foo() { top: 0, left: 0 }results in an error.{and}are essentially parsed as a function body (andtop: 0, left: 0as function body contents). -
What if it’s an expression?
Same thing. Treated as regular function syntax.
(function(x, y) { left: x, top: y }) // SyntaxErrorYou need to be careful to wrap returning object with parenthesis, so that curly braces aren’t considered to be part of a function production. In this case —
left: x, top: yresults in an error. But if returned object only contains one value, or no value at all, the silent behavior can make for an unexpected behavior down the road:(function() { x: 1 } )(); // returns `undefined` (function() ({ x: 1 }) )(); // Expression Closure; returns object with x=1 (function() {} )(); // returns `undefined` (function() ({}) )(); // Expression Closure; returns object -
Can we make function explicitly strict?
No.
function foo(x, y) "use strict"; 1 + 1; typeof foo(); // "string" (not "number")This obviously doesn’t work; “use strict” — which is a strict directive that’s supposed to make function behave as per ES5 strict mode rules — doesn’t behave in any special way. It’s simply treated as a string literal and so resulting function becomes identical to
function foo(){ return “use strict”; }(followed by a useless1 + 1statement).
The above is parsed as function f() alert("1st statement"); followed by alert("2nd statement"). Only first expression becomes an expression in the return statement of a function.
(This file was used for testing)
Main features
Well that was fun. To summarize some of the main traits:
- Function body should always be present
- Only first expression is consumed by an implicit return statement
- Can be used in place of both — function declaration and function expression (named or anonymous)
function idopt (argsopt) expris not fully identical tofunction idopt (argsopt) { return expr }(depends on context)- Precedence of call expression can be not obvious. For example:
(function()x+y)()vs.function()x+y()
Practical?
To my knowledge, expression closures are currently only supported by Mozilla-based browsers. They also aren’t supported by tools like Google Closure Compiler or YUI Compressor, which certainly makes it challenging to use them in production.
It’s not clear if more implementations will join Mozilla and add support for these shortcuts. Could they be part of an experimental road to Harmony (and its shorter function syntax) — the next version of the ECMAScript language? Do you already use expression closures in your work/experiments, or perhaps planning to? And would you change anything in their behavior?
Andy E said on Jan 17, 2011 @ 14:44
#1Hmmm… I think there’s a few pitfalls for very little gain if this becomes part of the ECMA-262 standard. Perhaps handy for a quick blast in the console but other than that I’m struggling to see the point. Surely there’s other, far more important ways of improving the language?
Dmitry A. Soshnikov said on Jan 17, 2011 @ 15:53
#2Good overview. I was using expression closures (as well, as shorthand for extracting object properties sugar, see below) in one project on SpiderMonkey — for a quick functional argument, e.g. even handler it’s a good candidate.
Current expression closures of SpiderMonkey are direct interpretation of the “one-line Python’s lambdas”. Also, as you noticed, currently there are considerations for a better syntactic sugar for “funargs” (the current “winner” seems is sharp —
#).Notice also, that SM has the sugared pattern matching for extracting properties (the same name as variables are created in the environment):
where
configis{x: 10, y: 20, z: 30}.P.S.: In points 15 and 16 you expected the semantics changes of labels, right? Nope, they are just still simple labels, and e.g. the example below doesn’t throw (assuming the changed semantics sugar as there would been properties of an object):
function foo(x) { top: 0 left: 0 } // OK, but - just labelsDmitry.
Dmitry A. Soshnikov said on Jan 18, 2011 @ 0:24
#3Ah, a typo. Not
but of course:
({x, y, z}) = config;Additional info on es: http://wiki.ecmascript.org/doku.php?id=strawman:object_initialiser_shorthand
The example was confused with CoffeeScript, where it’s not required grouping operator:
{x, y, z} = configDmitry.
Peter van der Zee said on Jan 18, 2011 @ 12:00
#4How does the EC behave in strict mode, so when it’s declared outside of it. I’d expect like a regular function, but you never know. Plus, the strict mode implementation is probably not very robust yet. Nice writeup, as usual :)
kangax (article author) said on Jan 20, 2011 @ 23:08
#5@Peter
It does seem to work as any other function, defined with regular syntax. For example:
(function() { "use strict"; (function f() this)(); // undefined })();FF’s strict mode implementation is actually the most robust at the moment (I think they’re close to 100% conformance).
@Andy
As far as I understand, shorter syntax is only part of the story. Implicit return — which is what’s hapenning here — is apparently important for tail-call optimizations. Brendan talked about this in a recent “A Minute with” podcast.
@Dmitry
In 15 I thought that
{ }would be interpreted as object initializer, not function body :) But regular function production clearly precedes there, and sofunction(){}is interpreted as a regular function expression (with empty body), rather thanfunction(){return {}}(which could have been desugared fromfunction(){}EC).Avdi Grimm said on Jan 21, 2011 @ 8:44
#6Even as someone who doesn’t spend much time in JS, I can appreciate geeking out over a language feature like this. Nice work!
Z Feuerborn said on Apr 13, 2011 @ 20:51
#8I have to questions the wisdom of this feature. Though an interesting concept, I cringe at the thought of some of the ternary statements I’ve seen. I think this falls into the same category as those. Awfully helpful in very specific use cases, but massive potential to create an unmaintainable mess. Still, I liked the article. Nice work.
None said on Jul 9, 2012 @ 19:29
#9Rather than mutilate javascript with Mozilla’s non-standard syntax…
Petition the W3C to include LISP as a web-language (I mean, that is what you ultimately want right? Ditching the ANSI structure and going full LISP, with very loose structure.)
Heck, that’d make all the “Javascript Macro Library” people happy (jQuery, Harmony) because LISP would give them the power to actually implement their ideas.