Currying and Uncurrying in JavaScript and Flow

Tools for composition of and insight into higher-order functions

Currying is a common concept in functional programming. Oftentimes it is presented as a convenient utility, or is seen as a subset of partial function application. This post will introduce currying (and its inverse, uncurrying), show that it can be necessary when trying to compose higher-order functions, and explain why it is distinct from partial function application in both intent and essence. Finally, we’ll see an example of how currying can be useful as a conceptual tool, allowing us to understand functions in new ways.

Definition

function tripleSum(a : number, b : number, c : number) : number {
return a + b + c;
}

And this is the curried equivalent:

function tripleSumCurried(a : number) : number => number => number {
return function (b : number) : number => number {
return function (c : number) : number {
return a + b + c;
}
}
}

The first function is called like this: const six = tripleSum(1, 2, 3), while the second function is called like this: const six = tripleSumCurried(1)(2)(3). For a function to be in curried form, each intermediate function must take exactly one argument.

Currying and uncurrying are not properly defined on functions which take a variable number of arguments (using the JavaScript arguments object or rest parameters) so such functions will be ignored in this post.

Automating currying

function curry2<A, B, R>(fn : (A, B) => R) : A => B => R {
return function (a : A) : B => R {
return function (b : B) : R {
return fn(a, b);
};
};
};
function curry3<A, B, C, R>(fn : (A, B, C) => R) : A => B => C => R {
return function (a : A) : B => C => R {
return function (b : B) : C => R {
return function (c : C) : R {
return fn(a, b, c);
};
};
};
};
// etc. We can go up to argument lists as long as we'd like

(“arity” is the number of arguments a function takes. curry2 works on functions with an arity of two, curry3 on functions with an arity of three, etc.)

Now, we can implement tripleSumCurried in terms of tripleSum:

const tripleSumCurried = curry3(tripleSum);

We can also write a single function capable of currying a function of any arity. This function’s type cannot be expressed in Flow, so it’s presented without type annotations:

function curry(fn) {
const collect = (argsSoFar, arg) => {
const collectedArgs = argsSoFar.concat([arg]);

return collectedArgs.length >= fn.length ?
fn.apply(null, collectedArgs) :
collect.bind(null, collectedArgs);
};
return collect.bind(null, []);
}

Uncurrying

function uncurry2<A, B, R>(fn: A => B => R) :
(A, B) => R {
return function(a: A, b: B) : R {
return fn(a)(b);
}
}
function uncurry3<A, B, C, R>(fn: A => B => C=> R) :
(A, B, C) => R {
return function(a: A, b: B, c: C) : R {
return fn(a)(b)(c);
}
}
// etc

We can also write a dynamic uncurry function which works for functions of any arity:

function uncurry(fn) {
return function(...args) {
return args.reduce((fn, arg) => fn(arg), fn);
}
}

Now we can take our tripleSumCurried function from before, and uncurry it, producing a function that’s equivalent to tripleSum.

const tripleSumUncurried = uncurry3(tripleSumCurried);
const six = tripleSumUncurried(1, 2, 3);

There aren’t many cases where we’d need to use uncurry in practice. This would be most likely to occur if we wanted to pass a curried function into a higher-order function that expects its argument in uncurried form, which is a niche case. uncurry has an important relationship with curry, though, in that they are inverses of each other. uncurry(curry(fn1)) returns a function that’s equivalent to fn1, and curry(uncurry(fn2)) returns a function that’s equivalent to fn2. In this way, curry and uncurry establish a mapping between functions — for every uncurried function, there exists a matching curried function, and vice versa. In mathematical terms, this kind of relationship is called an “isomorphism”.

The isomorphism described by currying and uncurrying is a property of functions in general, which transcends any particular language. In languages like JavaScript, functions are generally uncurried. In some functional languages like Haskell, functions are generally curried. No matter which is the default in a given language, functions still effectively have two forms, and if we write the functions curry and uncurry then we can use whichever form we need at a given time.

Practical benefits of functions in curried form

const squares = [1, 2, 3].map(x => power(2, x));

And this is what it would look like if power was in curried form:

const squares = [1, 2, 3].map(powerCurried(2));

We can see that the second one is more succinct, because we don’t need to explicitly write out the new function that’s passed to the map. This is a nice convenience, albeit a very small one. Currying gives us expressive power beyond decluttering, though. Consider this function:

function applyArray<A, B>(arr: Array<A>, fns: Array<A => B>)
: Array<B> {
const result = []; for (let i = 0; i < fns.length && i < arr.length; i++) {
result.push(fns[i](arr[i]));
}
return result;
}

This function allows us to take an array of functions [fn1, fn2, fn3...] and an array of items [item1, item2, item3...] and combines them to produce the array [fn1(item1), fn2(item2), fn3(item3)...]. Let’s use this this and the functions defined earlier to combine three arrays of numbers:

const numbers1 = [1, 2, 3],
numbers2 = [4, 5, 6],
numbers3 = [7, 8, 9];
const result = applyArray(numbers1,
applyArray(numbers2,
numbers3.map(curry(tripleSum))));
// result is [13, 15, 18]

This is a fairly elegant way to apply a function that takes three arguments to three arrays of those arguments. Without using a curried form of tripleSum, though, similar code is extremely difficult to write without resorting to imperative tricks like for loops. The flexibility that currying provides us becomes most important when our code makes heavy usage of higher-order functions.

Tradeoffs between curried and uncurried functions

  1. When partially applying an argument to a function, in order to pass it in to a higher order function, we have slightly less syntactic noise
  2. Curried functions may be used in complex chains and compositions of higher-order functions, in ways that are impossible for uncurried functions

1 is kind of nice, but at the end of the day a few extra characters isn’t usually a big deal. 2 is legitimately important though, and means that currying is an essential tool for building clean, composable solutions.

In JavaScript, the curried form of functions have two main downsides:

  1. Curried functions are slightly more verbose to call directly than uncurried functions: fn(a)(b)(c) rather than fn(a, b, c)
  2. Curried functions suffer a heavy performance penalty

1 might seem aesthetically displeasing to some people, but it’s really a trivial annoyance. 2, however, is a serious concern. Creating a function in JavaScript is a lot slower than just calling a function. Usually this isn’t a cost worth worrying about, because JavaScript is fast and most JavaScript code isn’t CPU-bound anyway. If we use curried functions everywhere, though, then every time we call a function with an arity higher than 1, we create one or more functions. As we compose functions this adds up fast, and can easily lead to hundreds or thousands of additional functions being created in order to run one high-level function, with a noticeable performance cost. For this reason, it’s generally best to curry functions when you need their benefits, and leave most functions uncurried.

Relationship between currying and partial function application

Definitionally, they’re completely separate categories of things. Partial application encompasses a family of techniques, with many possible implementations. Currying is strictly defined and permits no variation.

Practically, the two are used for different reasons. The primary benefit of partial application is that it allows us to specialize a more general function. The primary practical benefit of currying is that it lets us compose functions with higher-order functions more flexibly.

Finally, partial application is a practical set of tools for solving recurring problems in software. It’s primarily utilitarian and software oriented, and is not something you’re likely to come across outside of writing code. Currying is a mathematical concept, a particular isomorphism between functions, which we have access to in our code using the curry and uncurry functions. Equivalents to currying exist outside of software, and may be seen in set theory, type theory, and in other domains of mathematics.

Conceptual benefits of working with curried functions

In a previous post I introduced functors, data types for which a map function is defined. For a type F to be a functor, this function must follow certain rules, and must have this signature:

type map<A, B> = (A => B, F<A>) => F<B>

In that post we described F as a container or a context holding a value, and map as a way to apply a function to this held value. But that leaves out a very, very important facet of map. To see this second facet, we need the signature of map to be in curried form:

type map<F, A, B> = (A => B) => F<A> => F<B>;

Or equivalently, with parenthesis added for clarity:

type map<F, A, B> = (A => B) => (F<A> => F<B>);

Viewed like this, map's second nature becomes clear: map lets us take a function that operates on normal values, A => B, and “lift” it into a function that operates on functors, F<A> => F<B>. This is powerful! We saw in the functor post that functors show up everywhere, and can provide or represent all sorts of useful contexts. If we build our applications in terms of functors and pure functions, we can define functions on simple values, and use map to lift those functions into whatever context we need.

All of the sample code from this post may be found here.

Thanks to Lizz Katsnelson for editing this post

JavaScript developer with a focus on typed functional programming. He/him. https://jnkr.tech