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.
There are Finally Fat Arrows in Java
Java continues to add new features and make itself more useful to the public in due course. That said, there are many who have complained that the service never allowed them to put in the fat arrows that they wanted to. It was a glaring hole in the program, and it has now been clamped down on. People are finally able to get the fat arrow designs that they have been looking for without having to use another program to make it happen.
Make sure you take a look at the full scope of what Java has to offer you as you explore the promise that it will be something even grander than anything else out on the marketplace at this time. You will want to take advantage of these new arrow designs if you can, and you will want to check up on all of the latest offerings from Java whenever possible.
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 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; ;
//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…
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.
The argument’s 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.
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. this value is set to the 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();
It’s 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?
So…remember how there are five ways to define the value of this in a function?…
…well now there’s a sixth…
(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).
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.
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 hardbound short form functions and the necessary conceptual leap becomes more radical.
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.