Perfection Kills

by kangax

Exploring Javascript by example

A closer look at expression closures

January 17th, 2011

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

  1. Can EC be used to create function declarations (not just function expressions)?

    Yes.

    
    function f(x, y) x + y
    typeof f; // "function"
    
  2. What about named function expressions?

    Works as expected.

    
    typeof (function f(x, y) x + y); // "function"
    
  3. 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;"
    
  4. What about non-standard `name` property?

    Works as expected.

    
    function foo(x, y) x + y;
    foo.name; // "foo"
    
  5. How about immediately invoked function expression?

    Works fine.

    
    (function(x, y) x + y)(1, 2); // 3
    
  6. 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) following x + y actually applies to an inner expression, so instead of calling outer function — and assigning returned value to f2f2 is being assigned a function that has x + y(1, 2) as its body. Something to be aware of.

  7. How does EC work with ES5 accessor syntax (let’s try getter first)?

    Works!

    
    var obj = ({ get foo() 1 });
    obj.foo; // 1
    
  8. How about setter?

    Works!

    
    var _x;
    var obj = ({ set foo(x) _x = x });
    obj.foo = 5;
    
    _x; // 5
    
  9. 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'
    
  10. 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) });
    
  11. 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 inside return statement. And return 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";
    
  12. Can body of a function created via EC be omitted?

    Nope.

    
      function f() // Error
    

    You would think that function f() should be identical to function f(){ return }, but no, empty function body is not allowed there. It’s not clear why this was made like this. Parsing complexities, perhaps?

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

  15. 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 of x(function(){})() (rather than just x).

  16. 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 as function(){ return { … } } but the production rules of “regular” function syntax precede here, and function foo() { top: 0, left: 0 } results in an error. { and } are essentially parsed as a function body (and top: 0, left: 0 as function body contents).

  17. 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
    
  18. 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 useless 1 + 1 statement).

(This file was used for testing)

Main features

Well that was fun. To summarize some of the main traits:

  1. Function body should always be present
  2. Only first expression is consumed by an implicit return statement
  3. Can be used in place of both — function declaration and function expression (named or anonymous)
  4. function idopt (argsopt) expr is not fully identical to function idopt (argsopt) { return expr } (depends on context)
  5. 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?

Categories: ECMA-262, Harmony, Mozilla

Comments (9)

  1. Gravatar

    Andy E said on Jan 17, 2011 @ 14:44

    Hmmm… 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?

  2. Gravatar

    Dmitry A. Soshnikov said on Jan 17, 2011 @ 15:53

    Good 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):

    (x, y, z) = config;

    where config is {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 labels

    Dmitry.

  3. Gravatar

    Dmitry A. Soshnikov said on Jan 18, 2011 @ 0:24

    Ah, a typo. Not

    (x, y, z) = config;

    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} = config

    Dmitry.

  4. Gravatar

    Peter van der Zee said on Jan 18, 2011 @ 12:00

    How 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 :)

  5. Gravatar

    kangax (article author) said on Jan 20, 2011 @ 23:08

    @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 so function(){} is interpreted as a regular function expression (with empty body), rather than function(){return {}} (which could have been desugared from function(){} EC).

  6. Gravatar

    Avdi Grimm said on Jan 21, 2011 @ 8:44

    Even as someone who doesn’t spend much time in JS, I can appreciate geeking out over a language feature like this. Nice work!

  7. Gravatar

    Z Feuerborn said on Apr 13, 2011 @ 20:51

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

  8. Gravatar

    None said on Jul 9, 2012 @ 19:29

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

Trackbacks

Leave a Comment

Please, don't forget to escape your input (<, > and &). Wrap code sections with <pre>

Allowed tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>