Currying and Uncurrying in JavaScript and Flow

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

A curried function is a function which takes its arguments one at a time, returning a new function at each step. An uncurried function is a function which takes all of its arguments at once. For example, this is an uncurried function:

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

We performed the currying in tripleSumCurried by hand by manually wrapping that function’s logic in a series of returned functions, each of which closed over one argument. This is tedious, but we can write functions to curry any function of a given arity in a type-safe way:

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

The same way we can automatically curry a function, we can write a function to “undo” this currying. We’ll call this function “uncurry”, and we can write typed versions in a similar way to our currying functions:

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

The main reason that people use currying in JavaScript is that it can make code more succinct when working with higher level functions. For example, let’s say we have a power function which raises a number to a given exponent. We want use power to square each number in an array. This is what that code would look like if power was in uncurried 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

The benefits of function’s curried forms are twofold, as we just saw:

  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

Currying can be seen as a method of performing partial function application. A curried function takes arguments one at a time, partially applying them until it has all of them, and then executes. And if you compare the dynamic curry function in this post to the autoPartial function in my partial function application post, you’ll see that the two are extremely similar. But despite their similar behaviors, the two are really quite different:

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

The fact that currying is rooted in mathematics means it connects us to powerful systems for working with and reasoning about functions. One large benefit of curried functions is that they can help us gain insight into our functions that can be hard to glean from their uncurried forms.

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

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store