There’s a concept in functional programming called currying which gives you the ability to create one function from another, and have the created function call the original but with some arguments prefilled in. To be ultra-rigorous, we assume the original function has n arguments, and currying produces a chain of n functions each taking one argument to produce the same results as the original. We, however, will skip that rigorous definition since it’s too constricting for our discussion here. Instead we’ll talk about partial function application, but call it currying anyway. In essence: we want to call function A that takes lots of parameters, but since some/most of them are preset to some default values, we’d prefer having another function B that calls A filling in those presets to save time/effort.
Now, ordinarily, people explain currying with reference to mystical functions that add two numbers. I’m not happy with those types of examples because all I’m left with is a big feeling of “So What?” when I run through one of those.
Instead let’s illustrate what currying is about by looking at the setTimeout
function. This, as you should know, takes a function and an expiry time in milliseconds, and, after the time has expired will execute the function. Let’s suppose you’re writing some code and you notice that all your timeouts tend to be 10 milliseconds long (using a small timeout like this is a technique to split up long-running code and allow the execution engine time to “breathe”). If you are like me, you’d write a function that called setTimeout
with a default of 10 milliseconds:
var delay10 = function(f) { setTimeout(f, 10); };
Nothing too difficult. After a while, you notice that there are some other calls to setTimeout
that use a default of 5 seconds: for example, your code displays some element in the browser and if the user does not respond to it within 5 seconds you remove the element. Your first thought might be to write this next function:
var delay5secs = function(f) { setTimeout(f, 5000); };
But then there’s part of you that recognizes that this is code duplication. Tiny, but still there. You wrote it (just like I just did) using copy-and-paste and altered a single value. Because we understand that functions are objects in JavaScript, we should be able to write a function that takes an expiry time and that returns a function that calls setTimeout
with that delay:
var makeDelayFunction = function(time) { return function (f) { setTimeout(f, time); }; };
So here the makeDelayFunction
takes a time value and then returns a function that takes a function to execute and calls setTimeout
with that function and the original time value. Through the wonder of closures we encapsulate the original time argument and then use it in the function that’s returned. Our two delay functions can now get created like this:
var delay10 = makeDelayFunction(10); var delay5secs = makeDelayFunction(5000);
And, bingo, we’ve removed the code duplication. What we’ve just done is to curry the setTimeout
function (again, I’m using a looser definition of curry here). We’ve created a function that returns functions that call setTimeout
with a preset time value, and the preset value is stored in a closure. Now, admittedly, with my setTimeout
example the benefits aren’t that earth-shattering, but at least it shows the principle.
Let’s take it a little further now. The addEventListener
function takes three parameters: the event type (a string with various predefined values), the listener (a function that will be called when the event happens), and the capture flag (which for our purposes can be false
). The function is called on some element of the DOM. Let’s suppose we want to add mouse click listeners for a whole bunch of elements. Of the four parameters to the function (counting the object we’re calling it on as a parameter) we have two fixed parameters and two “floating”. Our first thought, given what we’ve learned above, is this:
var makeListener = function (type) { return function (element, func) { element.addEventListener(type, func, false); }; }; var addClick = makeListener("click"); addClick(myDiv, function () { alert("myDiv was clicked!"); });
Looks pretty good, but note the code duplication. It’s a little harder to spot perhaps, but it’s there. (In essence, we’re returning a function that calls another. The function being called has parameters that come from the closure, and others from the call itself.) Can we extract out this commonality?
It turns out that yes we can. The idea was first shown by Oliver Steele, and it needs us to use the arguments
array and a little trick. The little trick goes like this: we pass in a complete set of arguments we need for the wrapped function to the maker function. For those arguments that are fixed, we supply the actual values. For those arguments that are floating, we pass undefined
. The function we create then slots in the arguments it’s given into the undefined
spaces that were passed to the maker function. Perhaps looking at the call to the maker function for our first example might make this clearer:
var delay10 = makePartialFunction(setTimeout, undefined, 10); var delay5secs = makePartialFunction(setTimeout, undefined, 5000);
The new maker function, makePartialFunction
, takes the function to be wrapped as the first parameter, and then the remaining arguments are the arguments to that function when it’s to be called, with any unknowns passed as undefined
.
What might that maker function look like?
var makePartialFunction = function () { // the first argument is the function we're wrapping var func = arguments[0], // get the arguments pseudo-array as a local array defaultArgs = Array.prototype.slice.call(arguments); return function () { var actualArgs = [], // the arguments we'll call with arg = 0, i; // create the arguments for the call for (i = 1; (i < defaultArgs.length) && (arg < arguments.length); i++) { if (defaultArgs[i] !== undefined) { actualArgs.push(defaultArgs[i]); } else{ actualArgs.push(arguments[arg]); arg += 1; } } // call the wrapped function return func.apply(this, actualArgs); }; };
First of all we make a copy of the data passed to the maker function: the function to be wrapped and the arguments passed. Then we return a function. This function, when called, constructs an arguments array for the wrapped function, replacing the undefined
parts with arguments to its own call. It then calls the wrapped function using the apply invocation.
Sounds groovy, except it won’t work for my second example. Next time we’ll fix that problem.
Now playing:
Chemical Brothers - Setting Sun [Instrumental]
(from The Saint)
No Responses
Feel free to add a comment...
Leave a response
Note: some MarkDown is allowed, but HTML is not. Expand to show what's available.
_emphasis_
**strong**
[text](url)
`IEnumerable`
* an item
1. an item
> Now is the time...
Preview of response