Q: What is currying? Implement a curry function that supports partial application.

Answer:

Currying transforms a function that takes n arguments into a sequence of n unary functions:

f(a, b, c)   --currying-->   f(a)(b)(c)

Partial application is related but slightly different: it pre-fills some arguments and returns a function expecting the rest. A practical curry usually supports both — you can pass arguments one at a time, in groups, or all at once.

Why It Matters

  • Build specialized functions from generic ones (const add5 = add(5)).
  • Compose pipelines without anonymous lambdas everywhere.
  • Match the signature expected by point-free functional utilities.

Manual Currying

// Original
function add(a, b, c) { return a + b + c; }

// Curried by hand
const addCurried = a => b => c => a + b + c;
addCurried(1)(2)(3); // 6

Generic curry Implementation

function curry(fn, arity = fn.length) {
  return function curried(...args) {
    if (args.length >= arity) {
      return fn.apply(this, args);
    }
    return function (...more) {
      return curried.apply(this, args.concat(more));
    };
  };
}

const sum = (a, b, c, d) => a + b + c + d;
const cSum = curry(sum);

cSum(1, 2, 3, 4);   // 10
cSum(1)(2)(3)(4);   // 10
cSum(1, 2)(3, 4);   // 10
cSum(1)(2, 3)(4);   // 10

The trick: each invocation either has enough arguments to call the original or returns a closure that remembers what we have so far and keeps collecting.

Partial Application with Placeholders

Real-world libraries (Ramda, Lodash) support a placeholder so you can skip an earlier argument:

const _ = Symbol('placeholder');

function curryP(fn) {
  return function curried(...args) {
    const filled = args.slice(0, fn.length);
    if (
      filled.length >= fn.length &&
      filled.every(a => a !== _)
    ) {
      return fn.apply(this, filled);
    }
    return (...more) => {
      const merged = [...filled];
      let i = 0;
      for (const m of more) {
        const idx = merged.indexOf(_, i);
        if (idx === -1) merged.push(m);
        else { merged[idx] = m; i = idx + 1; }
      }
      return curried.apply(this, merged);
    };
  };
}

const greet = (g, name, punct) => `${g}, ${name}${punct}`;
const cGreet = curryP(greet);
const yell = cGreet(_, _, '!');
yell('Hi', 'Ada'); // "Hi, Ada!"

Practical Uses

const fetchFrom = curry((baseUrl, path) => fetch(`${baseUrl}${path}`));
const fromApi   = fetchFrom('https://api.example.com');
fromApi('/users');
fromApi('/orders');

const map = curry((fn, arr) => arr.map(fn));
const doubleAll = map(x => x * 2);
doubleAll([1, 2, 3]); // [2, 4, 6]

[!NOTE] Currying composes especially well with pipelines: pipe(map(double), filter(isEven), reduce(sum))(list).

Trade-offs

  • Currying adds function-call overhead. In hot loops that matters.
  • Stack traces become deeper and harder to read.
  • Variadic functions (fn.length === 0) need an explicit arity argument.