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.
Write curry(fn) with placeholder support.
Without placeholders it's a normal curry. It collects arguments over one or more calls. Once it has fn.length of them, it calls fn with all of them, including any beyond fn.length, and returns the result. Until then each call returns a new function, and partial results can be reused.
curry.placeholder (defined in the starter) reserves a position. The arguments of later calls fill the reserved positions from left to right, and any left over are appended. A placeholder passed later can fill a reserved position, which keeps it reserved.
fn is called as soon as there are at least fn.length arguments and none of the first fn.length is a placeholder.
const _ = curry.placeholder;
const list = curry((a, b, c) => [a, b, c]);
list(_, 2)(1, 3); // [1, 2, 3]
list(_, _, 3)(1)(2); // [1, 2, 3]
list(1)(_, 3)(2); // [1, 2, 3]
list(_, _, 3)(_, 2)(1); // [1, 2, 3]
const half = curry((a, b) => a / b)(_, 2);
half(10); // 5
half(8); // 4
const list = curry((a, b, c) => [a, b, c]);
expect(list(1)(2)(3)).toEqual([1, 2, 3]);
expect(list(1, 2)(3)).toEqual([1, 2, 3]);
expect(list(1, 2, 3)).toEqual([1, 2, 3]);
const _ = curry.placeholder;
const list = curry((a, b, c) => [a, b, c]);
expect(list(_, 2)(1, 3)).toEqual([1, 2, 3]);
const half = curry((a, b) => a / b)(_, 2);
expect(half(10)).toBe(5);
const _ = curry.placeholder;
const list = curry((a, b, c) => [a, b, c]);
expect(list(_, _, 3)(1)(2)).toEqual([1, 2, 3]);
expect(list(_, _, 3)(1, 2)).toEqual([1, 2, 3]);
const _ = curry.placeholder;
const list = curry((a, b, c) => [a, b, c]);
expect(list(1)(_, 3)(2)).toEqual([1, 2, 3]);
expect(list(_, 2)(1)(3)).toEqual([1, 2, 3]);
const _ = curry.placeholder;
let calls = 0;
const list = curry((a, b, c) => {
calls++;
return [a, b, c];
});
const waiting = list(_, 2, 3);
expect(typeof waiting).toBe("function");
expect(calls).toBe(0);
expect(waiting(1)).toEqual([1, 2, 3]);
expect(calls).toBe(1);
const _ = curry.placeholder;
const list = curry((a, b, c) => [a, b, c]);
expect(list(_, _, 3)(_, 2)(1)).toEqual([1, 2, 3]);
const _ = curry.placeholder;
const minus1 = curry((a, b) => a - b)(_, 1);
expect(minus1(10)).toBe(9);
expect(minus1(5)).toBe(4);
const pair = curry((a, b) => [a, b])(_);
expect(pair(1)(2)).toEqual([1, 2]);
expect(pair(3, 4)).toEqual([3, 4]);
const _ = curry.placeholder;
const collect = curry(function (a, b) {
return [...arguments];
});
expect(collect(_, 2)(1, 3)).toEqual([1, 2, 3]);
Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.
Ctrl + Enter runs the tests (⌘ + Enter on a Mac).
I'm here to help with Curry with placeholders. I give hints, never the solution, so you get to solve it yourself. Ask for a hint, select code to ask about it, or press Ask Tuto why on a failing test.
Your first 3 answers need no account. for free to keep asking.