In mathematics and computer science, currying is the technique of translating the evaluation of a function that takes multiple arguments into evaluating a sequence of functions, each with a single argument. For example, a function that takes two arguments, one from X
and one from Y
, and produces outputs in Z
, by currying is translated into a function that takes a single argument from X
and produces as outputs functions from Y
to Z
. Currying is related to, but not the same as, partial application
.
For instance, Currying is translating a function from callable as f(a, b, c)
into callable as f(a)(b)(c)
. Here is an example of how to implement curry in JS:
function curry(func) { return function(a) { return function(b) { return func(a, b); }; }; } // usage function sum(a, b) { return a + b; } let carriedSum = curry(sum); alert( carriedSum(1)(2) ); // 3
As you can see, the implementation is a series of wrappers.
curry(func)
is a wrapper function(a)
.sum(1)
, the argument is saved in the Lexical Environment, and a new wrapper is returned function(b)
.sum(1)(2)
finally calls function(b)
providing 2, and it passes the call to the original multi-argument sum
.In other words, when a function, instead of taking all arguments at one time, takes the first one and return a new function that takes the second one and returns a new function which takes the third one, and so forth, until all arguments have been fulfilled.
That is, when we turn a function call add(1,2,3)
into add(1)(2)(3)
. By using this technique, the little piece can be configured and reused with ease.
Let’s look at a simple add function. It accepts three operands as arguments, and returns the sum of all three as the result.
function add(a,b,c){ return a + b + c; }
You can call it with too few (with odd results), or too many (excess arguments get ignored).
add(1,2,3) --> 6 add(1,2) --> NaN add(1,2,3,4) --> 6 //Extra parameters will be ignored.
How to convert an existing function to curried version?
The curry function does not exist in native JavaScript. But library like lodash
makes it easier to convert a function to curried one.
//import or load lodash var abc = function(a, b, c) { return a + b + c; }; var curried = _.curry(abc); var addBy2 = curried(2); console.log(addBy2(0,0)); // => 2 console.log(addBy2(1,1)); // => 4 console.log(curried(4)(5)(6)); // => 15
Advanced currying allows both to keep the function callable normally and to get partials easily. To understand the benefits we definitely need a worthy real-life example. For instance, we have the logging function log(date, importance, message)
that formats and outputs the information. In real projects such functions also have many other useful features like: sending it over the network or filtering:
function log(date, importance, message) { alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`); }
Let’s curry it!
log = _.curry(log);
After that log still works the normal way:
log(new Date(), "DEBUG", "some debug");
…But also can be called in the curried form:
log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)
Let’s get a convenience function for today’s logs:
// todayLog will be the partial of log with fixed first argument let todayLog = log(new Date()); // use it todayLog("INFO", "message"); // [HH:mm] INFO message
And now a convenience function for today’s debug messages:
let todayDebug = todayLog("DEBUG"); todayDebug("message"); // [HH:mm] DEBUG message
So:
log
is still callable normally.In case you’re interested, here’s the “advanced” curry implementation that we could use above.
function curry(func) { return function curried(...args) { if (args.length >= func.length) { return func.apply(this, args); } else { return function(...args2) { return curried.apply(this, args.concat(args2)); } } }; } function sum(a, b, c) { return a + b + c; } let curriedSum = curry(sum); // still callable normally alert( curriedSum(1, 2, 3) ); // 6 // get the partial with curried(1) and call it with 2 other arguments alert( curriedSum(1)(2,3) ); // 6 // full curried form alert( curriedSum(1)(2)(3) ); // 6
The new curry
may look complicated, but it’s actually pretty easy to understand.
The result of curry(func)
is the wrapper curried that looks like this:
// func is the function to transform function curried(...args) { if (args.length >= func.length) { // (1) return func.apply(this, args); } else { return function pass(...args2) { // (2) return curried.apply(this, args.concat(args2)); } } };
When we run it, there are two branches:
args
count is the same as the original function has in its definition (func.length
) or longer, then just pass the call to it.func
is not called yet. Instead, another wrapper pass is returned, that will re-apply curried providing previous arguments together with the new ones. Then on a new call, again, we’ll get either a new partial (if not enough arguments) or, finally, the result.