I recently fully understood the concept of utilizing Currying in JavaScript. The core concept can be grasped from these references:
- Wikipedia entry on the theoretical concept
- John Resig’s explanation of the technique
- Stack Overflow discussion
So far, I only got the concept but couldn’t understand when and how to use it effectively. Let’s see:
Scenario
You are making a bunch of JSONP calls in a loop. You want the responses to fill in an array store appropriately, e.g. you want the response to look like this:
[JSONP Call 1 Response, JSONP Call 2 Response, JSONP Call 3 Response, ...]
Since JSONP calls are asynchronous, each of those individual calls can come back with the response at a different point in time. Hence, a simple loop never works. You will end up with responses in improper locations in the array store. Something like:
[JSONP Call 3 Response, JSONP Call 2 Response, JSONP Call 5 Response, ...]
If you have worked a little with JavaScript, you will most probably do something like this to counter the problem:
let arrayStore = []; // array to store the responses
// callback function to be called after each JSONP call is successful
let callback = function (index, response) {
arrayStore[index] = response;
};
let position = 0;
while (position < 5) {
// wrapping my callback function in an anonymous immediate function
// to create closure for my incrementing variable position
JSONP(url, (function (pos) {
return function (response) {
callback(response, pos);
};
})(position));
position++;
}
This is all good and fine. It works perfectly and doesn’t complain. However, if your while()
loop is itself inside more closure functions, things can quickly get out of control. Code maintainability decreases fast as there are way too many closures to track down.
Enter Currying!
Partially applying a function is a particularly interesting technique in which you can pre-fill-in arguments to a function before it is ever executed.
In the first code snippet, we were storing the position
inside a closure to persist the state for the returned callback. However, the above definition helps us rethink the problem like this:
Let’s pre-fill the callback function index
argument with the current position
value so that when the JSONP call completes and the callback is called, it already has that index
.
// the curry function that we will use
let _curry = function () {
let args = Array.prototype.slice.call(arguments);
let fn = args.shift();
return function () {
return fn.apply(this, args.concat(
Array.prototype.slice.call(arguments))
);
};
};
let arrayStore = []; // array to store the responses
// callback function to be called after each JSONP call is successful
let callback = function (index, response) {
arrayStore[index] = response;
};
let position = 0;
while (position < 5) {
// pre-filling callback function (with current position value)
// to be called later when the JSONP call returns
JSONP(url, _curry(callback,position));
position++;
}
Notice the simplified while()
loop now!
I found this to be oversimplified in terms of understanding what’s happening in the code. Personally, coming back days later and taking a peak at the first code snippet, wasn’t that easy as the closures were not self-explanatory. The second code gist with currying is much easier to wrap your head around.
Note: The version of curry function in the second snippet pre-fills arguments from the left. It is called left-curry. If you want to pre-fill arguments from the right, you will have to re-arrange the curry function a bit.