Currying in JavaScript

I was trying to implement something a bit like tabbing functionality in JavaScript the other day and ran into a problem. I had elements that would switch tabs – so I made these all of “tabswitch” class and cooresponding elements for the content of the “tab” class. I wanted to assign a onclick handler to a switcher element based on it’s position only. So clicking the 3rd switch element would switch to the 3rd tab. Something like this:

< .... class="tabswitch" onclick="show(3);">

But I wanted to do it in a way that didn’t involve writing this down for each element. I wanted to iterate through everything of the “switcher” class and assign it a handler when the page loaded.  Following on that path, it seems like you would want something like this in an onload handler:

var switchers = document.getElementsByClassName("tabswitch");
for(var i = 0; i < len; i++)
   switchers[i].onclick = function () {show(i)};

of course, this doesn’t really work out because when it comes time for the handler to execute, ‘i’ will be bound to a temp containing the last number assigned to ‘i’. So clicking any of these elements would switch to the last tab, not at all what we want! What we need is for the current value of ‘i’ to be “shoved” into the function when we assign this onclick handler.  One way of achieving this is by using “eval” to dump the entire contents of the function with the value for ‘i’ interpolated in the appropriate places (and even this only works if you don’t need to change i in the function). This is not very pretty.

Several languages have a beautiful solution to this problem called currying (and others have something called partial evaluation).  In this example, we would want assign the onclick handler to a new (“curried”) function that is defined like so:

var showi = show(i);
switchers[i].onclick = showi; 

(note: we’ll need a way to distinguish a call from a curried function, this is solved later) . so when the element is clicked, the showi function is called which returns the show function that will evaluate with the i from the for loop.  Currying in general lets you save and pass around functions that have already been “evaluated” for their first n arguments.  Another quick example:

function add (a, b) {
  return a + b;
}

var addthree = add(3);
var sum = addthree(2);

so the value of “sum” would be 5 in this hypothetical language with currying.

So currying doesn’t exist in the core JavaScript but several libraries have implemented their own versions.  I ended up using the one from the Prototype library.  After you place this in your code, any function can be curried:

Function.prototype.curry = function() {
  var method = this, args = Array.prototype.slice.call(arguments);
  return function() {
    return method.apply(this, args.concat(Array.prototype.slice.call(arguments)));
  }
};

If you know that Array.prototype.slice.call(arguments) just returns the arguments as an array, this is really pretty simple.  Going back to the adding example:

function add(a, b) {
  return a + b;
}

var addthree = add.curry(3);
var sum = addthree(2);

in this case, our call to add.curry(3) is essentially returning this:

function() {
    return add.apply(this, [3].concat(Array.prototype.slice.call(arguments)));
  }

and calling addthree essentially does this:

add.apply(addthree, [3, Array.prototype.slice.call(arguments))]);

which will call the add function with the saved 3 and the new arguments  (2), reducing to a call of add(3, 2), which is exactly what we want! This is just on-the-fly storage in closures, but it achieves our desired result in a clean way.

Note: if you want to use a different scope (for example, if your curried function is a member function that uses ‘this’) just modify the curry to take in the scope as the first argument:

Function.prototype.curry = function() {
  var method = this, args = Array.prototype.slice.call(arguments),
  scope = args.shift();
  return function() {
   return method.apply(scope, args.concat(Array.prototype.slice.call(arguments)));
 }
};


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 29 other followers