Introduction to currying in JavaScript

Implementations and practical examples

Hicham Benjelloun
DailyJS
7 min readJul 25, 2020

--

Photo by JJ Ying on Unsplash

Introduction

We want to specialize a JavaScript function of fixed arity by transforming it first into a composition of functions of lower arities.

First, consider a function of the following form:

In its simplest form, this problem consists of transforming this function into a new function of the following form:

We can also generalize this process to get the following form:

This last transformation is also called currying after the American mathematician Haskell Curry whose work laid the foundations of functional programming.

In this article, we propose to study different aspects of currying in JavaScript. In particular, we will see:

  • a simple example to understand what currying is in practice.
  • several JavaScript implementations allowing to carry out the transformations above.
  • a generic and flexible variant of transforming a JavaScript function into a composition of functions of variable arity.
  • an example illustrating the limits of the proposed implementations with regard to the currying of functions of variable arity.

A simple example of currying

We want to compute the price all taxes included (ATI) of products belonging to different categories using a function ATI:

Now let’s rewrite our function ATI in its curried form:

This allows us in particular to define functions for computing the price all taxes included by category:

We can thus rewrite our price computations including taxes without repeating the value of the used rate:

This is a very simple example of using currying so that we can then specialize a function by setting its first parameter.

Although this is not the subject of this article, note that we can also rewrite our functions ATI_service andATI_first_necessity as follows:

The resulting functions are called partial applications. We will also say that we have projected the ATI function on the rest of the parameters which have not been fixed. The advantage of this technique is to be able to set certain parameters and return a new function of lower arity.

Even if, as the example above shows, the projection of the ATI function on theprice parameter gives a function similar to the curried function, one must not confuse currying and partial application:

  • currying makes it possible to transform a function with several parameters into a composition of unary functions.
  • a partial application allows to fix a certain number of parameters of a function and returns a function of lower arity.

In this article, we will implement a generic currying function allowing the currying process to be carried out automatically, regardless of the number of parameters of the considered function.

We would like to be able to obtain, for example, the second ATI function from the first as follows:

Currying implementations

Simple currying

Consider a function of the following form:

We want to transform it into a new function of the following form:

Solution

Here is a possible solution:

Here, the curryOne function takes a function fn as a parameter and returns a new function taking a parameter first. When we call the unary function thus produced, we obtain a new function having memorized the first argument passed to the parameter first, and applicable to the rest of the parameters of the function fn.

A more concise solution

Note that the curryOne function can be rewritten in the following more concise way:

Indeed, the Function.prototype.bind() method returns here a new function with a bound argument which will be automatically placed before the other arguments which will be passed to it, which is very convenient to achieve the expected transformation.

General currying

Now consider the general form of the curried function we want to achieve:

Before tackling the implementation, let’s revisit our function curryOne:

And then let’s try to go a little further by applying a new simple currying transformation to the first function that is returned:

To be able to generalize this solution, we must first observe that in all cases, it is the last function that is responsible for calling the initial function fn on all the arguments. In addition, the arguments encountered during the course of the successive unary functions calls are stacked from left to right in the final call: a mechanism must therefore be found so that this function can store in the correct order all the arguments passed successively to the unary functions.

Solution

Here is a possible solution corresponding to our previous observation:

We build here a curried function curried using an ascending recursion corresponding to the accumulation inacc of the arguments passed successively to the unary functions:

  • Terminal case: all the arguments have been consumed and the length of the accumulator of arguments is equal to the arity fn.length of the function.
  • If all the arguments have not yet been consumed, then we return a unary function p => curried (... acc, p). In particular, we observe that at each call of curried, the length of the accumulator is incremented by one and our recursion ends when its length reaches the arity of the function fn.

A more concise solution

Note that in the same way as in the previous section, we can write a more concise function using the Function.prototype.bind() method which naturally accumulates the arguments on the left.

Flexible currying

We may need a little more flexibility when currying a function.

Say we want to be able to specialize a function by specifying multiple arguments at once instead of making multiple function calls:

Solution

We can simply take the general currying function seen previously and replace the unary functions with functions of variable arity:

Also, we can simplify this solution. This is because we were using an intermediate constant curried to make sure that we return a unary function, which is what the call to the function curried does. Now, since our curry function can take any number of arguments as parameters, we can rewrite the function curry without any intermediate function:

A more concise solution

The use of the Function.prototype.bind() method allows us to write the previous function more concisely, by implicitly accumulating the arguments in the function returned on each recursive call:

Limitations of the proposed implementations

Here is an example of a nifty generic function that lets us compute a sum of numbers in several ways:

Now let’s try to reuse the generic curry function we wrote earlier to make this code more readable:

Oops! As you probably expected, the line calling curry does not produce the correct result: this is because for a function fn accepting a variable number of arguments, the value of fn.length which defines the terminal case of our recursion is… 0!

Thus, we fall directly into the terminal case of our recursive function and our attempt at currying is equivalent to:

This is of course not what we are trying to do, but this problem allows us to stress that our currying function only applies to a function with a fixed arity.

In case you still need to curry functions of variable arities, you can always modify the code of the function curry to take into account an additional parameter which would indicate the expected number of parameters: our function must know when to stop and return a value.

That being said, we can generalize our function curry in some cases. For example, by generalizing the sum function seen at the beginning of this section, we can write:

This is a clever way of currying functions of variable arity. Note, however, that it is necessary to tell JavaScript that we want to extract the resulting value (by casting the returned function by prefixing it with +) since our curried function always returns a function when called and does not know in advance where we are going to stop! So the trick of using the valueOf property allows us to extract the value that interests us when we need it.

I originally wrote this article in French and you can find the original version here:

--

--