Skip to the code
cf.

Curry with placeholders

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

What the tests check

  1. works like a normal curry without placeholders
    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]);
    
  2. a placeholder reserves a position for a later argument
    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);
    
  3. later arguments fill placeholders from left to right
    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]);
    
  4. appends what's left once the placeholders are filled
    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]);
    
  5. doesn't call fn while one of its arguments is a placeholder
    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);
    
  6. a placeholder can fill a placeholder, keeping it reserved
    const _ = curry.placeholder;
    const list = curry((a, b, c) => [a, b, c]);
    expect(list(_, _, 3)(_, 2)(1)).toEqual([1, 2, 3]);
    
  7. partial results can be reused
    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]);
    
  8. passes extra arguments through to fn
    const _ = curry.placeholder;
    const collect = curry(function (a, b) {
      return [...arguments];
    });
    expect(collect(_, 2)(1, 3)).toEqual([1, 2, 3]);
    

Learn more

solution.jsSaved in this browser

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