Write curry(fn) that collects arguments over several calls until it has fn.length of them.
Before you start
What's currying?
Currying turns a function that takes several arguments, such as add3(a, b, c), into one you call one argument at a time: add3(a)(b)(c). Each call takes an argument and returns a new function waiting for the next. The call that brings the last argument runs the original function and returns its result. It's named after the logician Haskell Curry.
// A curried add, written by hand: each call returns a function waiting for the next argument
const add = (a) => (b) => a + b;
add(2)(3); // 5
const add5 = add(5); // a function waiting for b, with a fixed at 5
add5(1); // 6
add5(10); // 15: add5 can be reused, and nothing changes it
[1, 2, 3].map(add5); // [6, 7, 8]
That's what makes currying handy: a half-applied function such as add5 is a new, more specific function you can store, reuse, and pass to map, filter, or an event listener. You fix the argument that stays the same once, and supply the one that varies later.
Writing every function this way is tedious, so helpers such as Lodash's _.curry curry an ordinary function for you. A function's length property is the number of parameters it declares before the first one that has a default value or starts with ...: 3 for (a, b, c) => a + b + c, but 0 for (...nums) => 0. The curried version collects arguments until it has that many, then calls the original. Lodash's version also accepts several arguments in one call. With const add3 = _.curry((a, b, c) => a + b + c):
| Call | Arguments so far | Returns |
|---|---|---|
add3(1) | 1 | A function waiting for more |
add3(1)(2) | 1, 2 | A function waiting for more |
add3(1)(2)(3) | 1, 2, 3 | 6: it now has as many arguments as the original expects, so it calls it |
add3(1, 2)(3) | 1, 2, 3 | 6 |
add3(1, 2, 3) | 1, 2, 3 | 6 |
Two variants are worth knowing. Lodash's _.curry also accepts a placeholder, _, that holds a position open for a later call: with const subtract = _.curry((a, b) => a - b), subtract(_, 1) fixes b and waits for a, so subtract(_, 1)(10) is 9. And when a function can't know how many arguments are coming, such as a sum that adds up whatever it's given, the caller ends the chain with an empty call: sum(1)(2)(3)() returns 6.
Currying is close to partial application, which fixes some arguments in one step and returns a function that takes all the rest in its next call. A curried function keeps returning functions until it has every argument, so you can fill them in a piece at a time.
curry(fn) returns a function that collects arguments over as many calls as it takes. Once it has at least fn.length of them, it calls fn with all of them and returns the result. Until then, each call returns a function that takes more.
So with const add3 = curry((a, b, c) => a + b + c), the calls add3(1)(2)(3), add3(1, 2)(3), add3(1)(2, 3), and add3(1, 2, 3) all return 6.
Each partial result can be reused. If the calls bring more than fn.length arguments, pass the extra ones to fn too. fn gets the this of the final call.
function curry(fn) {
// your code
}
// const add3 = curry((a, b, c) => a + b + c);
// add3(1)(2)(3); // 6
// add3(1, 2)(3); // 6
// const add1 = add3(1);
// add1(2)(3); // 6
// add1(10)(20); // 31: add1 still holds only the 1
Solve it in the editor, with tests
The answer
Write it yourself first. Then compare it with a reference solution and the checks it passes.
Checking your sign-in…