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:
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 + y
actually applies to an inner expression, so instead of calling outer function — and assigning returned value tof2
—f2
is 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 }); // SyntaxError
In this case, we might want
bar = 1, baz = 2
as 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"; // Error
This is pretty understandable, considering that expression after
function()
in EC is treated as an expression insidereturn
statement. Andreturn
statement 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() // Error
You 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
foo
function — 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 } // SyntaxError
You 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: 0
as function body contents). -
What if it’s an expression?
Same thing. Treated as regular function syntax.
(function(x, y) { left: x, top: y }) // SyntaxError
You 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: y
results 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 + 1
statement).
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) expr
is 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?
Did you like this? Donations are welcome
comments powered by Disqus