cf.completefrontend

Write the codeHard

Extend curry(fn) with a placeholder, so add3(_, 2)(1, 3) fills the skipped argument later.

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):

CallArguments so farReturns
add3(1)1A function waiting for more
add3(1)(2)1, 2A function waiting for more
add3(1)(2)(3)1, 2, 36: it now has as many arguments as the original expects, so it calls it
add3(1, 2)(3)1, 2, 36
add3(1, 2, 3)1, 2, 36

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.placeholder is a unique value; call it _. Passing _ reserves a position: that argument is left out for now and filled in by a later call.

Each later call fills the reserved positions from left to right with its arguments, and appends any that are left over. With const add3 = curry((a, b, c) => a + b + c), the calls add3(_, 2)(1, 3), add3(_, _, 3)(1)(2), and add3(1)(_, 3)(2) all return add3(1, 2, 3).

Call fn once its first fn.length arguments are all real values, not placeholders. Partial results can be reused.

Write this
function curry(fn) {
  // your code
}
curry.placeholder = Symbol("curry.placeholder");
 
// const _ = curry.placeholder;
// const divide = curry((a, b) => a / b);
// const half = divide(_, 2); // a is reserved, b is 2
// half(10); // 5: 10 fills a
// half(3);  // 1.5: half can be reused
//
// const add3 = curry((a, b, c) => a + b + c);
// add3(_, _, 3)(1)(2); // 6: 1 fills a, then 2 fills b

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…