Currying in javascript

8/28/2021

What is currying?

Currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each take a single argument.

So currying transforms fn(a, b, c…n) to fn(a)(b)(c)…n) . Let’s dive right in to an example.

const sum = (a, b) => a + b;

const curriedSum = curry(sum);

console.log(curriedSum(1)(2)); // = 3

The curriedSum function converts the sum function into a sequence of two functions since the original sum function needs two arguments.

Naive implementation

Based on the above example, we can implement a naive version of the curry function. So curriedSum returns a function which accepts one argument b. This is passed as the second argument to the function being curried. The first argument is stored in the function closure.

function curry(fn) {
  return function (a) {
    return function (b) {
      return fn(a, b);
    };
  };
}

Currying with variadic arguments

The previous implementation has a few problems

  1. It only works for functions which accept 2 arguments.
  2. The context value is not being propagated accurately.

Let’s try implementing a generic currying function which supports variadic arguments.

There are a couple of key insights behind the new implementation.

  1. Function.length returns the arity of the function. The arity of a function is the number of parameters expected by the function.

For example, consider this multiply function which returns the product of three numbers;

const multiply = (a, b, c) => a * b * c;
console.log(multiply.length); // 3
  1. We have to keep collecting arguments until we have enough arguments, i.e. till the number of arguments are greater than or equal to the arity of the function.
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      // Execute the original function when we have enough arguments
      return fn.apply(this, args);
    } else {
      return (...nextArgs) => curried(...args, ...nextArgs); // otherwise keep on adding arguments
    }
  };
}

We can use bind for a more concise implementation.

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return curried.bind(this, ...args);
    }
  };
}

Infinite sum

This happens to be a popular interview question based on recursion. So here’s the infinite sum function that we have to implement

sum(1)() // 1
sum(3)(2)() // 5
sum(1)(2)...(n)() // (1 + 2 + 3 ...n)

We can make some observations based on the above example

  1. The sum function returns the sum when we call it with undefined.
  2. If the function is called with a valid number another function is returned
const sum = (a) => {
  return (b) => {
    if (b === undefined) {
      return a;
    } else {
      return sum(a + b);
    }
  };
};

Infinite sum with variadic arguments

sum(1, 2)(3)(4, 5)(); //15

This is a bit more tricky. We are not dealiing with single arguments anymore.Try implementing this on your own before checking the solution.

Solution
const sum = (...args) => {
  return (...moreArgs) => {
    if (moreArgs.length === 0) {
      return args.reduce((a, b) => a + b);
    } else {
      return sum(...args, ...moreArgs);
    }
  };
};

Currying with placeholder support

We are not done with currying yet, currying with placeholder support is something I might tackle in the future.