What is currying?
Currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each take a single argument.
So currying transforms fn(a, b, c…n) to fn(a)(b)(c)…n)
. Let’s dive right in to an example.
const sum = (a, b) => a + b;
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)); // = 3
The curriedSum
function converts the sum function into a sequence of two functions since the original sum function needs two arguments.
Naive implementation
Based on the above example, we can implement a naive version of the curry function. So curriedSum
returns a function which accepts one argument b
. This is passed as the second argument to the function being curried. The first argument is stored in the function closure.
function curry(fn) {
return function (a) {
return function (b) {
return fn(a, b);
};
};
}
Currying with variadic arguments
The previous implementation has a few problems
- It only works for functions which accept 2 arguments.
- The context value is not being propagated accurately.
Let’s try implementing a generic currying function which supports variadic arguments.
There are a couple of key insights behind the new implementation.
- Function.length returns the arity of the function. The arity of a function is the number of parameters expected by the function.
For example, consider this multiply function which returns the product of three numbers;
const multiply = (a, b, c) => a * b * c;
console.log(multiply.length); // 3
- We have to keep collecting arguments until we have enough arguments, i.e. till the number of arguments are greater than or equal to the arity of the function.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
// Execute the original function when we have enough arguments
return fn.apply(this, args);
} else {
return (...nextArgs) => curried(...args, ...nextArgs); // otherwise keep on adding arguments
}
};
}
We can use bind for a more concise implementation.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return curried.bind(this, ...args);
}
};
}
Infinite sum
This happens to be a popular interview question based on recursion. So here’s the infinite sum function that we have to implement
sum(1)() // 1
sum(3)(2)() // 5
sum(1)(2)...(n)() // (1 + 2 + 3 ...n)
We can make some observations based on the above example
- The sum function returns the sum when we call it with undefined.
- If the function is called with a valid number another function is returned
const sum = (a) => {
return (b) => {
if (b === undefined) {
return a;
} else {
return sum(a + b);
}
};
};
Infinite sum with variadic arguments
sum(1, 2)(3)(4, 5)(); //15
This is a bit more tricky. We are not dealiing with single arguments anymore.Try implementing this on your own before checking the solution.
Solution
const sum = (...args) => {
return (...moreArgs) => {
if (moreArgs.length === 0) {
return args.reduce((a, b) => a + b);
} else {
return sum(...args, ...moreArgs);
}
};
};
Currying with placeholder support
We are not done with currying yet, currying with placeholder support is something I might tackle in the future.