JavaScript Fat City

It’s official! We’re getting a new function syntax! The TC39 group (the panel charged with delivering ES 6) has reached consensus on an abbreviated syntax for JavaScript function expressions. Its popularly known as the fat arrow syntax, and is based on a similar construct found in CoffeeScript.

Make no mistake, I’m delighted that we will finally have an alternative to the unnecessary clunkiness and verbosity of the present grammar, but I can’t shake a nagging feeling that this proposal (in its current form) is flawed to the extent that it might actually make new developers more confused than they already were. I’ll run through the key features of this new construct, then explain my concerns and how they might be mitigated.

BS Alert
Before starting out I should let you know that I’m going to make a lot of assertions about how fat arrows work. I’m reasonably confident that most of them are consistent with the latest proposal, but since research material is scant (I’m relying on the ES Wiki and the ES discuss list) and the examples are not testable (the traceur compiler does not yet support fat arrows) there are going to be some mistakes, for which I apologize upfront. I welcome corrections and will update the content as I get them. Thanks!

How does it Work?
The Syntax
The fat arrow grammar has the following characteristics:
1. The arrow (=>) takes the place of the function keyword
2. Parameters are specified before the arrow, parentheses are required when there are zero, two or more parameters.
3. Block syntax (i.e. enclosing the function body in curly braces) is optional when the body comprises a single expression, otherwise it is required.
4. The return keyword is implied when the function body comprises a single expression. In all other cases returns must be used explicitly.

Here are some simple examples. I’ve paired each fat arrow use case with the corresponding long-form syntax, although as we’ll see later the paired functions do not necessarily represent identical behavior. I’m defining variables with the var keyword for the sake of familiarity, but by the time ES6 is implemented you’re more likely to use let which allows variables to be defined with block scoping:

//empty function
var fat1 = () => {};
var long1 = function() {};

//return the square
var fat2 = x => x * x;
var long2 = function(x) {return x * x};

//add two numbers
var fat3 = (a, b) => a + b;
var long3 = function(a, b) {return a + b};

//return square root if x is a number, otherwise return x 
var fat4 = x => (typeof x == "number") ? Math.sqrt(x) : x;
var long4 = function(x) {
  return (typeof x == "number") ? Math.sqrt(x) : x;
};

Fat arrows bring a terse elegance to functional JavaScript…

//return a new array containing the squares of the original...
[1, 2, 3, 4, 5].map(x => x * x); //[1, 4, 9, 16, 25]

//capitalize...
['caption', 'select', 'cite', 'article'].map(word => word.toUpperCase()); 
//['CAPTION', 'SELECT', 'CITE', 'ARTICLE']

//rewrite all instances of Fahrenheit as Celsius...
function f2c(x) {
  var test = /(\d+(\.\d*)?)F\b/g;
  return x.replace(test, (str, val) => (val-32)*5/9 + "C");
}
f2c("Store between 50F and 77F"); //"Store between 10C and 25C"

(The last example is a rewrite of this traditional implementation).

No extras for you, fat arrow
Not only do fat arrows use lightweight syntax – they also generate lightweight functions…

no constructors
Functions created using fat arrow syntax have no prototype property, which means they can’t be used as constructors. If you try to use a fat arrow function as a constructor it throws a TypeError.

no arguments
The arguments object is not available within the execution context of a fat arrow function. This is not a huge loss; by the time ES 6 is in full swing, we can expect arguments to have been deprecated in favor of the rest (...) syntax.

no names
There are Function Expressions, and then there are Named Function Expressions. Fat arrow functions have no place for a name, so they will always just be plain anonymous Function Expressions.

The value of this
Functions defined with the fat arrow syntax have their context lexically bound; i.e. the this value is set to the this value of the enclosing scope (the outer function where present, otherwise the global object).

//with long-form inner function
var myObj = {
  longOuter: function() {
    console.log(this); //this is myObj
    var longInner = function() {
      console.log(this); //this is global object
    };
    longInner(); 
  }
}

myObj.longOuter();

//with fat arrow inner function
var myObj = {
  longOuter: function() {
    console.log(this); //this is myObj
    var fatInner = () => 
      console.log(this); //this is myObj
    fatInner(); 
  }
}

myObj.longOuter();

Its a hard binding, which means if a fat arrow is used to define a method in an object literal it will continue to be bound to that object even when invoked from a borrowing object:

var myObj = {
  myMethod: function() {return () => this;},
  toString: () => "myObj" 
}

var yourThievingObject = {
  hoard: myObj.myMethod,
  toString: () => "yourThievingObject"
};

yourThievingObject.hoard(); //"myObj"

Similarly the this value of a fat arrow function cannot be modified by means of call or apply:

//traditional long inner function
var myObj = {
  longOuter: function() {
    console.log(this); //this is myObj
    var longInner = function() {
      console.log(this); //this is now myOtherObj
    }
    longInner.call(myOtherObj); 
  }
}

myOtherObj = {};
myObj.longOuter();

//new fat inner function
var myObj = {
  longOuter: function() {
    console.log(this); //this is myObj
    var fatInner = () => 
      console.log(this); //this is still myObj
    fatInner.call(myOtherObj); 
  }
}

myOtherObj = {};
myObj.longOuter();

So What’s the Problem?
If you trawl the JavaScript section of Stack Overflow, you’ll find dozens of questions from puzzled developers trying to get their heads around JavaScript’s somewhat byzantine process of this assigment.

So…remember how there are five ways to define the this value of this in a function?…

Syntax of function call Value of this
1. Method call:
myObject.foo();
myObject
2. Baseless function call:
foo();
global object (e.g. window)
(undefined in strict mode)
3. Using call:
foo.call(context, myArg);
context
4. Using apply:
foo.apply(context, [myArgs]);
context
5. Constructor with new:
var newFoo = new Foo();
the new instance
(e.g. newFoo)

…well now there’s a sixth

Syntax of function call Value of this
6. Fat Arrow:
(x => x*x)();
this of lexical parent

(A seventh rule was also proposed – naming the first argument of a fat arrow as ‘this’ would have bound the context to the base reference of a method call – but thankfully that option has been deferred).

I appreciate the rationale behind lexical this binding. It’s intuitive, and if JavaScript was starting over, this would not be a bad way to do it. But at this point I’ve fallen in love with dynamic this values; they make functions gloriously flexible and are a great complement to functional patterns, wherein functions form the bedrock of data, and other objects are mere fungibles.

Moreover, if new developers are already discouraged by JavaScript’s perceived arbitrary assignment of context, yet another rule might be enough to finish them off for good. Bear in mind that fat arrow is sugar, and a very tasty sugar at that; it will be eagerly devoured by many developers long before the consequences of the sixth law of this has time to sink in.

There’s another, related issue with the current proposal. Legacy functions (third party or otherwise) generally assume that their function arguments have dynamic this values. This enables them to invoke function arguments in any given context, which, amongst other things, is a useful way to add mixins.

It’s true that Function.prototype.bind already offers a form of hard binding, but it does so explicitly; on the other hand, fat arrow’s hard binding is a side effect and it’s not at all obvious that it would break code like this:

function mixin(obj, fn) {
  fn.call(obj);
}

//long form function mixin is dynamically bound
var withCircleUtilsLong = function() {
  this.area = function() {return this.radius * this.radius * Math.PI};
  this.diameter = function() {return this.radius + this.radius};
}

//fat arrow function mixin is lexically bound (to global object in this case)
var withCircleUtilsFat = () => {
  this.area = function() {return this.radius * this.radius * Math.PI};
  this.diameter = function() {return this.radius + this.radius};
}

var CircularThing = function(r) {this.radius = r};

//utils get added to CircularThing.prototype
mixin(CircularThing.prototype, withCircleUtilsLong); 
(new CircularThing(1)).area(); //3.14

//utils get added to global object
mixin(CircularThing.prototype, withCircleUtilsFat); 
(new CircularThing(1)).area(); //area is undefined

How to Fix It
OK, enough whining; time to make some proposals. Here are three ideas to remove, or at least mitigate, any negative effects of the new fat arrow context behavior.

1) (This one’s easy). Have fat arrow functions define this the same way as any regular Function Expression does – i.e. according to the five rules in the table above. It’s worth noting that CoffeeScript defined fat arrow as an alternative to their thin arrow (->) syntax. Thin arrow in CoffeeScript behaves, by and large, the same way as a regular JavaScript Function Expressions. In contrast ES6′s fat arrow is attempting to do at least two things at once – be the sole abbreviator of the syntax and redefine context assignment. To do one, or the other, would be less confusing.

2) (You probably saw this one coming too). Introduce thin arrow syntax at the same time. That way developers are drawn to the safer, less radical sugar which simply abbreviates their Function Expressions without throwing in secret surprises that mess with their contexts. Fat arrow expressions become the special case not the default. This mail suggested the difference between fat and thin arrow would confuse folks, but by removing thin arrow we remove the stepping stone between dynamically bound long form functions and hard bound short form functions and the necessary conceptual leap becomes more radical.

3) (This one was proposed by @fb55 on the es discuss list). Only employ lexical scoping as a fallback when no other this binding is suggested. In other words this would take the value of the base reference in a method call, or the context passed with a call or apply, but would defer to lexical scoping when invoked as a standalone function. (Standalone functions might just be the only part of JavaScript this assignment that actually needs fixing anyway).

Wrap Up
Is the primary goal of arrow functions brevity? or a hard-lexical binding? If it’s the former (and even if it isn’t a lot of developers will perceive that it is) then we should be careful not to overload it with new or surprising behavior. Oh and follow @fat.

Further Reading
ES Wiki: Arrow Function Syntax
The ES Discuss List: arrow function syntax simplified

About these ads

31 thoughts on “JavaScript Fat City

  1. At first I wondered if “wiwth” was an acronym I didn’t know yet. Do you mean “with,” as in “with long-form inner function”?

  2. You don’t consider using fn.bind(context) to be one of the ways to change the this value inside a function? You could also consider passing a second argument to one of the array iterator functions like arr.forEach(console.log, console) to be yet another way to change the this value.

    (Even if JS is just doing call invocation internally for both of these)

    So I’d say that there are 8 ways to change the this value inside of a function!

    • was going to add bind (it gets an honorary mention) but like you say, bind creates a wrapper function (though there are plans to change that in ES6) so its a little different

  3. I’m wondering about the optional block syntax, when you want to imply the return an object as your single expression. That is an exception to the “Block syntax is optional when the body comprises a single expression, otherwise it is required” rule, yes?

    var getMeasurements = radius => {{
    area: radius * radius * Math.PI,
    diameter: radius + radius
    }}

    Maybe unless you explicitly return?

    var getMeasurements = radius => return {
    area: radius * radius * Math.PI,
    diameter: radius + radius
    }

    Are those two examples both valid syntax, and equivalent?

    • @Brian Good questions :). I suspect that the second form would be more idiomatic. The first form I think would also work, but is certainly confusing. Especially given that double curly syntax has been co-opted by other DSLs like templating languages.

    • To return an objet, you may wrap it into parentheses to differenciate it from a block :

      var getMeasurements = radius => ({
      area: radius * radius * Math.PI,
      diameter: radius + radius
      })

  4. Pingback: JavaScript Fat City | Node.js | Scoop.it

  5. Reminds me of Perl. The only person who can maintain a Perl script is the creator, and only if they have worked on it in the last 6 months. I really hope they don’t allow these to be nested.

  6. Pingback: JavaScript gets a fat arrow and I’m not a fan | Evan Hahn dot com

    • Hi Michal,
      Right, Brendan is proposing long-form function for dynamic |this|.
      Option 3 is winning for me now. Though part of me would like all binding rules to be identical regardless of syntax. After all, the biggest complaint we get is about JS quirks.

  7. I wonder maybe it should be called ‘lambda’ and not ‘new function syntax’. It works nearly as lambda apart of ‘return’ behavior.
    Basically we’re like one leg in function and other in lambda, what purists can say about that?

  8. Good to see there is progression in javascript. Thanks for the indepth article. But that syntax is very unreadable. When I look at it, I see the ‘greater than or equal to’ (but wrong written ofcourse), which has nothing to do with function-declaration (in my opinion). Kinda offtopic, but I would love to have the ||= operator (logical OR assignment) in javascript.

  9. I think it is a real step back: while the whole world goes towards semantics, this short function syntax seems like a noisy line.

    It makes me think to some nightmare syntax from Perl, while new wave code like jQuery succeeds in producing human readable code which is truly readable like plain english.
    I can’t believe this proposal has been really considered.
    The funny is that the “=>” operator is two-chars long: if the team is really willing to shorten the “function” keyword, it could be successfully replaced with the notorious “fn” abbreviation, which would be much clearer for the most part of programmers.

    Please don’t do that, do not create regexp-like languages!
    Go ahead with semantics instead!

  10. Pingback: JS vai suportar => | Ricardo BinĀ“s Blog

  11. Options 1 and 2 seem feasible, though 1 would be very disappointing. Yes, having syntactic sugar is nice, but it’s not really sugar if I’m always having to re-bind “this” to somewhere useful, or wrap it in calls to bind() or somesuch. The most annoying thing to me about JavaScript closures now isn’t the function(){} syntax, but the inevitable “var that = this;” at the top of every scope where I’m likely to use it.

    But option 3 needs the most work. I like it in principle, but in practice, there’s only one way to dynamically build an array of arguments and pass it to a method, and that’s with Function.apply(), which also requires adding a new context to the given function.

    So, right now, the user of a library might need to be careful, in that their fat arrow might not have the “this” context the library author intended, in which case, at worst, a long version might be required. On the other hand, if the user expects lexical scoping, but a library sometimes overrides the given ‘this’ — yes, we want this sometimes, but if it happens unexpectedly, that makes it too easy to introduce this bug in the library code, and too hard for a user of that library to avoid tripping over it.

    Also, CoffeeScript exists. I’m not opposed to changes like this — they’ll certainly improve my experience with Chrome’s dev tool window — but if something can be fixed with a preprocessor, I don’t care as much. I’d much rather attack semantic issues like weak typing. (I’m not advocating static typing, just strong dynamic typing, like Ruby and Python have.)

    • Thanks – some good points there.
      You would only have to rebind if the function referenced ‘this’. And even then I think I prefer settable ‘this’ to having it set for you with no regard for your actual intent

  12. Pingback: JavaScript Fat City | node web programming | Scoop.it

  13. //fat arrow function mixin is lexically bound (to global object in this case)

    `this` should never be bound to `window`, or any other global object, because all ES.next features are supposed to be available only in strict mode, meaning that `this` will be `undefined`, not `window`. (It will bomb out, but that’s better than trashing your globals.)

  14. I wonder if the gotcha behaviour would be averted if fat.call and fat.apply were to throw exceptions if passed non-undefined/null as their first argument rather than just silently ignoring the value?

  15. Just a little pedantic note. On the second paragraph after your function rule table, you put the following sentence: they make functions gloriously flexible and are a great *compliment* to functional patterns.

    I think you mean “complement” as compliment means something completely different.

    Sorry, pedant-o-rant over now :-)

  16. Aside from the issues with “this”, the parentheses are required, but they’re not, but they are, seems confusing. Same for the curly braces. Considering all best practices say to always use them for if statements and for loops and such, it seems odd that they would be allowed to be optional. I also think the implicit, but not, return value could be confusing. However I DO like the no curlys and implicit return when it is passed to a method, as in the array.map example, and feel it should be restricted to this type of usage. Otherwise all you are saving is a few characters.

  17. Pingback: JSSpy » Javascript fat city

  18. Personally I hate this new syntax, it’s a lot less clear at a glance compared to the syntax of old and as a subjective opinion looks uglier too. Then again I’m not a fan of Coffeescript either so maybe it’s an issue of personal taste but I like my C style grammar.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s