Javascript: If `bind` made sense
Javascript has a triumvirat of function-amending methods: bind, call, and apply. They all modify a function, by setting its internal call property (this-reference) and deciding the arguments that are given to it. These three functions, as it happens, provide a pretty bad interface for doing just that.
I care a lot about interfaces in code. I believe that interfaces should work for the user, hide away complexity. I am willing to accept more complex code in exchange for a simpler and more easily usable interface.
Javascript has a long history, and faces a challenge that no other languages face: it has to stay backwards compatible. This means that if one new version introduces some bad syntax (or bad naming), it sticks.
For example, the name of XMLHttpRequest hasn’t changed, despite its highly inconsistent capitalization (all-caps “XML”, and camel cased “Http”).
A fun fact about bind, call, and apply is that if you give them a single argument they do exactly the same thing, except that bind does not invoke the function it is called on:
fun.bind(x)();
// ... is equivalent to ...
fun.call(x);
// ... is equivalent to ...
fun.apply(x);
bind #
The
bind()
method creates a new function that, when called, has itsthis
keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
It works like this:
//setting the internal call property of a stateful function
function statefulFun(a) {
return this.x + a;
}
var statefulFunWithThis = statefulFun.bind({ x: 2 });
statefulFunWithThis(2); //returns 2 + 2 = 4
//currying the function, creating a new function
function fun(a, b, c) {
return a + b + c;
}
var curriedFun = fun.bind(null, 1, 2, 3);
curriedFun(); //returns 3 + 2 + 1 = 6
Notice how, to best explain what bind does, I had to give two examples, each of different functionality: setting the internal call property of a function, and currying a function.
Currying means creating a new function that is like the original function, but has some values pre-set. In the case above, curriedFun
has all three arguments set, which means that all that can be done is invoking it. It is also possible to pre-set only some of the arguments.
I strongly dislike methods doing more than one thing. Functions should do one thing, without side-effects.
It would make sense to have two separate methods for these two separate functionalities. If bind didn’t allow currying, but only bound internal call properties, a new function, curry, could serve the other purpose:
//currying the function, creating a new function
function fun(a, b, c) {
return a + b + c;
}
var curriedFun = fun.curry(1, 2); // <-- no unneccessary "this"
curriedFun(1); //returns 3 + 2 + 1 = 6
It would still be possible to set internal call property and curry a function in a single line of code. Both curry and bind return a new function — they can be chained.
//binding internal call property *and* currying
function statefulFun(a, b) {
return this.x + a + b;
}
var modifiedFun = statefulFun.bind({ x: 3 }).curry(2);
modifiedFun(2); //returns 3 + 2 + 2 = 7
I have often needed to curry a function without necessarily binding an internal call property. In current Javascript, I would have to use a third party library, like lodash, to do this.
call #
The
call()
method calls a function with a giventhis
value and arguments provided individually.
function statefulFun(a) {
return this.x + a;
}
statefulFun.call({ x: 2 }, 2); //returns 2 + 2 = 4
Call can set both internal call property and arguments of a function. Call, unlike bind, invokes the function with these properties. The only difference between call and normal function invocation (fun()
) is the ability to add a this-reference.
To be consistent with the splitting of responsibilities I started with bind (do one thing), a better behaviour for call would be simply doing the exact same thing as normal function invocation:
function statefulFun(a) {
return this.x + a;
}
statefulFun.bind({ x: 2 }).call(2); //returns 2 + 2 = 4
It makes sense to have a separate method to invoke a function, because functions are first-level constructs, just like objects, and objects have named methods. The normal invocation will then be a simple short-hand. Examples of similar short-hands exist:
// function invocation with short-hand
fun.call();
fun();
// array creation with short-hand
var arr = new Array();
var arr = [];
// object creation with short-hand
var obj = new Object();
var obj = {};
In addition, explicitly using call, instead of the short-hand, improves readability in some cases:
//hard to read!
fun.bind(obj)(1, 2, 3);
//much better!
fun.bind(obj).call(1, 2, 3);
//hard to read!
obj[keyForFunction]();
//much better!
obj[keyForFunction].call();
apply #
The
apply()
method calls a function with a giventhis
value andarguments
provided as an array (or an array-like object).
Apply differs from call only in that it takes an array of arguments, instead of each argument individually.
I would suggest doing the same thing to it as was done to call: remove the binding of the this-reference from it. Focus on the ability to call functions with an array of arguments.
var numbers = [ 1, 7, 4, 2, 3 ];
function fun(a, b, c) {
return a + b + c;
}
fun.apply(numbers); //returns 1 + 7 + 4 = 12
the missing method #
It seems to me, in all this, that a function is missing. If I want to programatically build a number of arguments (without knowing how many I will get), I can invoke a function with these by using apply. But I can’t curry the function with an array of arguments.
The existing bind-method would simply pass an array as the first argument. Now that I’m dreaming up a better interface for bind/call/apply/curry, I think I would like to add a method that curries with an array:
fun.curryArray([1, 2, 3]);
// ... is equivalent to ...
fun.curry(1, 2, 3);
The final interface for modifying functions now has a simpler bind, and the following argument-handling functions:
arg list | array of args | |
---|---|---|
invoking | call | apply |
currying | curry | curryArray |
The names aren’t perfect, but the general interface is much nicer and more covering.