CoffeeScript : Why is function binding not the default?
As of a few days ago I started to play around with CoffeeScript and node.js. Sure I have used JavaScript in the past, like everyone else, but I have never used it enough or taken the time to fully understand the commonly misunderstood scope / context / prototype aspects of the language. If you don't yet understand it, I direct you to the plethera of articles about the topic.
I wanted to talk about the syntax subtlety of ->
vs =>
. The CoffeeScript site briefly mentions it as function binding, aka the Fat Arrow. Using =>
ensures that the this
context is always the same as when the function was defined, rather than in the context of the object it is currently attached to. As the site mentions the fat arrow is particullary helpful when using callbacks, of which you will be using a "few" ;). I think this is probably the most common mistake when first programming JavaScript / CoffeeScript. You make a function call passing a callback which references this
and just expect it to work... surprise!
Its a very subtle difference and most resources skim over the subject or simply ignore it. The free book, The Little Book on CoffeeScript, has a chapter on classes and does a good job explaining the important syntax difference.
I am not the only one who has mentioned this. I guess the argument is that you can generally avoid the extra call to __bind
because some/most of your functions may never be used in a different context and the this
scope will always be the same. Thats fine, but this creates many inconsistencies in the code, and for what real benefit? While I agree there is merit to having the deeper understanding of when to use =>
vs ->
properly, I tried to figure out why the unexpected has been adopted as the default.
Why not show the fat arrow some love?
The most widely found explanation says to not use it all the time for performance reasons. Everyone seems to just say ok, makes sense, in the name of performance I'll just use ->
as the standard and remember to use the fatty =>
when I need to. Inevitably you will forget and waste time tracking it down. Is this potential premature optimization worth the inconsistencies and unexpected behaviour? This smells of my old PHP days when everyone would always use single quotes for strings unless of course you had to inject a variable, then you would drop back to double quotes. I also remember even Facebook developers saying they didn't worry about the type of quotes they used - of course they "simply" built a PHP to C++ compiler to overcome their challenges.
Performance? Really?
To see if the madness should continue in the name of performance I have created a simple little benchmark.
class FunctionBinding
constructor: ->
thisIsNotBound: ->
thisIsBound: =>
You can see below that the compiled JavaScript wraps the thisIsBound
function with the __bind
call to bind the this
context for all calls to that function. This is where the extra overhead comes from.
(function() {
var FunctionBinding, fb, i,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
FunctionBinding = (function() {
function FunctionBinding() {
this.thisIsBound = __bind(this.thisIsBound, this);
}
FunctionBinding.prototype.thisIsNotBound = function() {};
FunctionBinding.prototype.thisIsBound = function() {};
return FunctionBinding;
})();
}).call(this);
Here is the benchmark code that executes the two functions and performs the necessary timing thanks to the node.js STDIO api console.time() and console.timeEnd(), that according to console.js is just a simple wrapper on Date.now()
.
fb = new FunctionBinding()
console.time('thisIsNotBound');
fb.thisIsNotBound() for i in [1..1000000]
console.timeEnd('thisIsNotBound');
console.time('thisIsBound');
fb.thisIsBound() for i in [1..1000000]
console.timeEnd('thisIsBound');
I first ran this with 10, 100, 1000, 10000 iterations but both timings were 0ms throughout. It wasn't until I got to the 100k and 1 M mark that I saw real numbers. These are averages of about 10 runs each.
console.time('marker') | 100k | 1 M |
---|---|---|
thisIsNotBinded | 1 ms | 10 ms |
thisIsBinded | 5 ms | 22 ms |
Looking at the numbers you could make the argument that it takes over twice as long to execute the function with the __bind
compared to without. That would be a fair statement. I think the numbers also show that the inconsistency in the code and potential for unexpected behaviour this causes is not worth the negligable performance improvement. After running this I can confidently say that a 12 ms difference on 1 M functions calls is most definitly a shallow over optimization.
What about memory?
From what I know so far the methods and properties of the prototype object are not duplicated for each instance and therefore don't add memory overhead with each instance. The binding code from above adds an instance function to call the prototype with the proper this
scope. This would add overhead which would be a reason to not use unnecessarily.
Enjoying the tangent
Don't get me wrong here. Despite this post, I am enjoying my current tangent. I am experiencing first hand the positive productivity and conciseness of CoffeeScript and the single threaded event loop of node.js. I see another blog rewrite in my future... express.js anyone!
CoffeeScript is only one of the many projects listed on the altJS page. Some of those are fairly interesting but it seems CoffeeScript is the most popular, at least according to github.
Are you drinking the new kool-aid? uh Java? I mean Coffee?