As I've said before, I'm quite partial to JavaScript. Lately, I've been reading about currying functions, so let's see if currying can be done in JavaScript.
The begs the question, What is currying? Currying is a technique to transform a function that takes some number of arguments to a function that takes fewer. The process was named after the logician Haskell Curry. The goal is to get something that works like this:
function add(a, b) { return a + b; } alert(add(3, 4)); // 7 var add_3 = add(3); alert(typeof add_3); // function alert(add_3(4)); // 7
Select what you want to copy and in doing so you will keep the formatting when pasting it. |
When we pass fewer than the required number of arguments, it returns a function that takes the remaining arguments. Once we pass the correct number of parameters, it evaluates the function.
Before possibly reinventing the wheel, I looked online to see if anyone had done this. While there are plenty of JavaScript functions that claim to curry, they all get it wrong. They do something like this (using the same add from above):
var add_3 = curry(add, 3); alert(add_3(4)); // 7 alert(typeof add(3)); // number
Select what you want to copy and in doing so you will keep the formatting when pasting it. |
They do get the add_3 function in the end, but all they do is create a new function with fewer arguments. Any calls to add will still behave like any other JavaScript function with too few arguments; the remaining arguments will be undefined. The add_3 function (both times) is called a partial function.
So how do we do proper currying in JavaScript? Here's what I did. It takes three steps.
First, we need a way to change a function's arity. Arity is the number of parameters a function accepts. This may not seem directly relevant, but trust me:
Function.prototype.toArity = function (n) { var func = this; var parmString = ''; var funcString = ''; var i; if (n == func.length) { return func; } if (n == 0) { return function () { return func.apply(this, arguments); }; } for (i = 0; i < n; ++i) { parmString += 'a' + i; if (i < n - 1) { parmString += ','; } } funcString = '(function (' + parmString + ') { return func.apply(this,
arguments); })'; return eval(funcString); };
Select what you want to copy and in doing so you will keep the formatting when pasting it. |
This function looks complicated, but it's not really. The basic idea is to wrap the function call in another function of the desired arity. They only way to do this in JavaScript is to create the function at runtime with eval. I know most people (myself included) advocate against eval, but in this case it is truly the only way. So this function will convert something like this:
function iTake3Args(a, b, c) { return a + b; // Ignore c }
Select what you want to copy and in doing so you will keep the formatting when pasting it. |
via this call:
iTake3Args.toArity(2);
Select what you want to copy and in doing so you will keep the formatting when pasting it. |
to something like this:
function anonymous(a0, a1) { return func.apply(this, arguments); }
Select what you want to copy and in doing so you will keep the formatting when pasting it. |
It may look ugly, but it works. And no-one should be printing the internals of the function anyway (they'll be calling it).
So now that we can control the arity of functions, we can generate partial functions (what all the libraries do) correctly (with the property arity). The function goes like this:
Function.prototype.partial = function () { var partialArgs = Array.prototype.slice.call(arguments, 0); var func = this; var retFunc = function () { var args = Array.prototype.slice.call(arguments, 0); return func.apply(this, partialArgs.concat(args)); }; return retFunc.toArity(Math.max(0, func.length - partialArgs.length)); };
Select what you want to copy and in doing so you will keep the formatting when pasting it. |
There's quite a bit going on here. Why is the arguments object being passed to slice? The arguments object is only just that, an object. We need to convert it to a proper array so we can concatenate the two argument arrays in the partial function. Then the return function (with arity 0) is converted to the proper arity. The function works similarly to the other curry functions:
var add_3 = add.partial(3); alert(add_3(4)); // 7
Select what you want to copy and in doing so you will keep the formatting when pasting it. |
Now we can finally get to currying properly. Here's the function:
Function.prototype.toCurriable = function () { var func = this; var retFunc = function () { if (arguments.length < func.length) { return func.partial.apply(func, arguments).toCurriable(); } return func.apply(this, arguments); }; return retFunc.toArity(func.length); };
Select what you want to copy and in doing so you will keep the formatting when pasting it. |
If you've understood everything so far, this shouldn't be too out there. If there aren't enough arguments, a partial function will be returned. Otherwise, the function is evaluated. The return function (with arity 0) is converted to the proper arity. So finally, we can recreate our initial example (with a couple changes):
var add = function (a, b) { return a + b; }.toCurriable(); alert(add(3, 4)); // 7 var add_3 = add(3); alert(typeof add_3); // function alert(add_3(4)); // 7
Select what you want to copy and in doing so you will keep the formatting when pasting it. |
Note the change to the definition of add. It has to be a function expression rather than a function definition because we want the result of toCurriable, not the original function.
This is by no means a perfect solution. While it preserves arity, it does not preserve scope. Things like:
someObj.func(3)(4);
Select what you want to copy and in doing so you will keep the formatting when pasting it. |
won't work as expected, because the second function call is evaluated in global scope rather than on someObj. To get the expected result, one should program:
(someObj.func(3)).call(someObj, 4);
Select what you want to copy and in doing so you will keep the formatting when pasting it. |
This evaluates both functions in the scope of someObj. I don't see any way around this ugly syntax at the moment.
Also, this solution relies on closures and eval, which means it will be memory intensive and will be slow in some browsers. I have not tested this in any browsers, only in Mozilla's Rhino, a command-line JavaScript environment. I imagine it will work in any Mozilla product. I make no claims about Internet Explorer.
I do, however, feel that this is an elegant and unobtrusive solution that has not been done before. This was a purely theoretical endeavor. True currying is definitely possible in JavaScript.
I hope someone found this useful. You can download all of the above functions in the attached JavaScript file.