Skip to the code
cf.

Curry

MediumFunctions

Write curry(fn). It returns a function that collects arguments over one or more calls. As soon as it has at least fn.length arguments, it calls fn with all of them and returns the result. Until then, each call returns a new function that waits for more.

A call may pass several arguments at once, or none, which adds nothing. Partial results can be reused: calling one never changes another. Extra arguments beyond fn.length are passed through to fn, and the this of the final call is passed to fn.

const add3 = curry((a, b, c) => a + b + c);
add3(1)(2)(3);  // 6
add3(1, 2)(3);  // 6
add3(1)(2, 3);  // 6
add3(1, 2, 3);  // 6
 
const add1 = add3(1);
add1(2)(3);     // 6
add1(10)(20);   // 31

What the tests check

  1. calls fn once it has fn.length arguments, one per call
    const add3 = curry((a, b, c) => a + b + c);
    expect(typeof add3(1)).toBe("function");
    expect(typeof add3(1)(2)).toBe("function");
    expect(add3(1)(2)(3)).toBe(6);
    
  2. accepts several arguments per call
    const add3 = curry((a, b, c) => a + b + c);
    expect(add3(1, 2)(3)).toBe(6);
    expect(add3(1)(2, 3)).toBe(6);
    expect(add3(1, 2, 3)).toBe(6);
    
  3. doesn't call fn before it has enough arguments
    let calls = 0;
    const add3 = curry((a, b, c) => {
      calls++;
      return a + b + c;
    });
    add3(1);
    add3(1)(2);
    expect(calls).toBe(0);
    add3(1)(2)(3);
    expect(calls).toBe(1);
    
  4. partial results can be reused
    const add3 = curry((a, b, c) => a + b + c);
    const add1 = add3(1);
    expect(add1(2)(3)).toBe(6);
    expect(add1(10)(20)).toBe(31);
    const add1and2 = add1(2);
    expect(add1and2(3)).toBe(6);
    expect(add1and2(4)).toBe(7);
    
  5. a call with no arguments adds nothing
    const multiply = curry((a, b) => a * b);
    expect(typeof multiply()).toBe("function");
    expect(multiply()(3)()(4)).toBe(12);
    
  6. passes extra arguments through to fn
    const collect = curry(function (a, b) {
      return [...arguments];
    });
    expect(collect(1)(2, 3)).toEqual([1, 2, 3]);
    
  7. calls a function with no parameters on the first call
    expect(curry(() => 42)()).toBe(42);
    
  8. passes the this of the final call to fn
    const add = curry(function (a, b) {
      return this.base + a + b;
    });
    const obj = { base: 10, add };
    expect(obj.add(1, 2)).toBe(13);
    const other = { base: 100, finish: obj.add(1) };
    expect(other.finish(2)).toBe(103);
    

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