JavaScript a clever way to implement currying
In this article, we are going to explore two different implementations of currying
in JavaScript.
First example
Let's consider first
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));
}
}
};
}
Here's how we use it
function sum(a, b, c) {
return a + b + c;
}
let curriedSum = curry(sum);
alert( curriedSum(1, 2, 3) ); // 6, still callable normally
alert( curriedSum(1)(2,3) ); // 6, currying of 1st arg
alert( curriedSum(1)(2)(3) ); // 6, full currying
First, from let curriedSum=curry(sum)
we get function curried(...args)
returned to call upon subsequently. In fact, curriedSum(1,2,3)
is in effect calling curried(1,2,3)
.
If we call curriedSum(1,2,3)
, from the if
statement we can see that we are simply executing sum(1,2,3)
. This is because func.length==args.length
in this case (which is 3).
If we are doing partial calls like curriedSum(1)
, we are now executing else
portion of the statement and we get returned function(...args2)
. So if we proceed to do curriedSum(1)(2,3)
, in effect we are doing function(2,3)
and this calls our function curried
again (curried.apply(this, args.concat(args2))
). But this time we are passing ...[1,2,3]
as arguments to our curried
function (due to concat
). So this time we are calling curried(1,2,3)
. But now args.length
equals func.length
which is 3 arguments required by sum
function. So, this time we are calling sum(1,2,3)
and 6 is returned.
So curried
function was used recursively to test if all required arguments to sum
function were supplied (in which case sum
function is evaluated), and if not, construct the next partial function.
For instance, in our example we first called curriedSum(1)
then curriedSum(1)(2,3)
which finally resulted in 6.
Second example.
const curry =(fn) =>{
return curried = (...args) => {
if (fn.length !== args.length){
return curried.bind(null, ...args)
}
return fn(...args);
};
}
const totalNum=(x,y,z) => {
return x+y+z
}
const curriedTotal = curry(totalNum);
console.log(curriedTotal(10) (20) (30));
Let's start with curriedTotal(10)
. This triggers calling of function curried(10)
. Since not all arguments to function totalNum
is yet provided, we execute what's inside if
statement. This uses bind
call to return a copy of curried(10)
. That is a copy of function curried
with the first argument of 10 provided.
Next is curriedTotal(10)(20)
. This calls curried_copy(10,20)
. Again we create another copy of curried_copy(10,20)
and return it as a function.
Next, we do curriedTotal(10)(20)(30)
. This calls curried_copy_of_copy(10,20,30)
. Now fn.lengh==args.length
, which is 3 in this case, so it now returns totalNum(10,20,30)
which is 60.