Currying

16 Oct 2023 . javascript .

Break down a function into a series of functions that each a single argument.

For starters, it accepts only one argument and returns a function. The returned function also accepts one argument and also returns a function. This cycle keeps going until the returned function is the one accepting the last argument. This function, the last one in the chain, finally returns the sum.

function sum3(x) {
  return (y) => {
    return (z) => {
      return x + y + z;
    };
  };
}
console.log(sum3(1)(2)(3)) // 6

Before we reach the last level, we won’t have all the arguments in the execution scope. This means that we create a wrapper function that collects the arguments and calls the real function. All the intermediate nested functions are called accumulator functions.

function _sum3(x, y, z) {
  return x + y + z;
}

function sum3(x) {
  return (y) => {
    return (z) => {
      return _sum3(x, y, z);
    };
  };
}

sum3(1)(2)(3) // 6  <--  It works!

Curry Wrapper

function curry(fn) {
  return (x) => {
    return (y) => {
      return (z) => {
        return fn(x, y, z);
      };
    };
  };
}

const sum3 = curry((x, y, z) => {
  return x + y + z;
});

sum3(1)(2)(3) // 6  <--  It works!

Recursive Curry

function curry0(fn) {
  return fn();
}

function curry1(fn) {
  return (a1) => {
    return fn(a1);
  };
}

function curry2(fn) {
  return (a1) => {
    return (a2) => {
      return fn(a1, a2);
    };
  };
}

function curry3(fn) {
  return (a1) => {
    return (a2) => {
      return (a3) => {
        return fn(a1, a2, a3);
      };
    };
  };
}

...

function curryN(fn){
  return (a1) => {
    return (a2) => {
      ...
      return (aN) => {
        // N-th nested function
        return fn(a1, a2, ... aN);
      };
    };
  };
}

We obviously need a terminal case else our code will form an infinite fractal. We will maintain a variable i that keeps track of our current level. The terminal case will be when i === N.

function nest(fn, i) {
  return (x) => {
    if (i === fn.length) {
      return fn();
    }

    return nest(fn, i + 1);
  };
}

function curry(fn) {
  return nest(fn, 1);
}

Next up, we need to store the accumulated arguments and pass them to fn() in the terminal case. The easiest solution is to create an array args inside curry and pass it to nest. Just add a guard for 0-arity functions and we are done.

function nest(fn, i, args) {
  return (x) => {
    args.push(x);

    if (i === fn.length) {
      return fn(...args);
    }

    return nest(fn, i + 1, args);
  };
}

function curry(fn) {
	if (fn.length === 0) {
    return fn;
  }

  const args = [];

  return nest(fn, 1, args);
}