Currying by Hand

March 01, 2022 • 7 min read Russian Dolls

Currying is a technique that allows us to take a function, fix it's arguments and output more specilised functions. In this article we are going to look at some examples of how currying can be used. We will then breakdown how they work and with a manual approach look at how we can build our own.

A Demonstration

I first read about currying a long time ago. The examples from the various Javascript books of the time were usually along the lines of the following:

function curry(func) {
    ... clever stuff here
}

function add(num1, num 2) {
    return num1 + num2
}

var add5 = curry(add)
document.writeln(add5(6)) // 11

I remember thinking this was all very clever, and then forgot about currying for almost another decade. A lack of imagination on my part, but I couldn't think of many use cases for these addFive or addTen functions. Had they have been labelled as addStandardDelivery and addExpressDelivery the penny might have dropped.

It's for that reason I would like to start with a simple but hopefully realistic example to get a flavour of what we are trying to achieve. In the following examples we will add VAT and import duty to some given figures — keeping it light!

We will start with a non-curried version and move onto a curried version for comparison.

// Non-Curried version

// Standard function to add percentages
const addPercentage = (percentage, num) => (100 + percentage) * num / 100

const VAT = 20
const IMPORTDUTY = 2.5
const figures = [10, 20, 30, 40]

// Add taxes to the figures with Array map.
figures
    .map(figure => addPercentage(VAT, figure))
    .map(figure => addPercentage(IMPORTDUTY, figure)) 

// Outputs
[12.3, 24.6, 36.9, 49.2]

The standout here is near the end of the code, where we add the taxes to our figures using Array.map. The code is actually quite readable, but there is still a small amount of logic to reason with and it has required wrapping our calls to addPercentage in anonymous functions.

Now for a more functional approach using currying.

// Curried Version

// Curried function to add percentages
const addPercentage = (percentage) => 
    (num) => (100 + percentage) * num / 100

const addVAT = addPercentage(20)
const addImportDuty = addPercentage(2.5)
const figures = [10, 20, 30, 40]

figures
    .map(addVAT)
    .map(addImportDuty)

In this example, we already have our functions ready to go. When we assigned addVat and addImportDuty we called the curried version of addPercentage. The percentage arguments were fixed for us and unary functions, that is a function that takes one argument — in this case a number, were returned.

Given that array map's job here is to pass figures from an array to a given function, and that addVat and addImportDuty only require one argument in the form of a number to do their calculation we can provide these two functions as is.

As a result there is no logic to reason with here and it is clear at a glance what we are trying to achieve. Furthermore these functions are re-usable, we don't need to type out anonymous functions each time, and therefore our code is less prone to errors.

Composition

I would like to take this one step further by introducing a technique called Composition. We can pass a number of unary functions to our compose function and a create one function that essentially wraps them.

The composed function expects a single argument which is passed to the wrapped functions in sequence. Each function returns it value as an argument to the next function finally outputting a value. You can think of compose being almost like a conveyor belt with the functions being a sequence of stages in a production line.

I don't expect you to fully grasp this, if you are new to this concept it is going to seem a bit alien. What I would like you to do though is observe the following code.

...
const addVAT = addPercentage(20)
const addImportDuty = addPercentage(2.5)

// make a new function out of the two curried functions
const addTaxes = compose(addImportDuty, addVAT)
...
// add nasty taxes
figures.map(addTaxes)

The key thing I want to demonstrate here, is how through the use of currying we can write clean, succinct and at a glance understandable code. Not only that but through composition how curried functions can then be used as the building blocks for more powerful functions.

A Breakdown of Manual Currying

Let's now take a look into manually building curried functions and how they work.

Again let's start with two examples. This time following that great example from the past, adding two numbers!

Here again is the standard binary function that takes two arguments, returning a result.

function add(number1, number2) {
    return number1 + number2
}

And here is the curried version written out in long-form.

function add(number1) {
    return function(number2) {
        return number1 + number2
    }
}

The major difference here is in the way we call these two functions. In the first binary function, we pass in two arguments and immediately receive a value.

add(10, 20) // returns 30

In the second, being a unary function we can only pass one argument. On calling that function, the inner function is returned instead of a final total value.

add(10) // returns function(number2) { return number1 + number2 }

If we want to receive the total in one line of code, we would need to immediately call that returned function with a second value.

add(10)(20) // 30

We need to have a look at the use of closures in curried functions. We can see the returned inner function takes only one argument, number2.

// inner function
function(number2) { return number1 + number2 }

It's clear looking inside the function return number1 + number2 that to do it's calculation it also requires number1. Fortunately thanks to closure the function is returned with a bundle containing number1 and the value that was assigned to it when we initally called the outer add function — in this example the number 10.

When we then call this returned function it is able to do a search of the attached closure to see if it can find the necessary variable and value.

It is through this process that we are able to fix arguments to our functions.

const addTen = add(10) 
// returns and assigns to addTen
function(number2) { number1 + number2 } // with a closure { number1: 10 }

// each time we call addTen passing in a value to number2
// it is able to find number1's value in it's closure
addTen(10) // 20
addTen(25) // 35

We can take this process further with more deeply nested curried functions.

function add(number1) {
    return function(number2) {
        return function(number3) {
            return number1 + number2 + number3
        }
    }
}

It is worth noting that in the case of deeply nested functions that if variables can't be found inside the inner function itself they will be sourced by first looking inside the containing function, then if not found there inside the next containing function and so on all the way up to the global object. This is called the scope chain.

For each containing function if the necessary variable or variables are found, a closure is created. This means a deeply nested function can be bundled with numerous closures.

const add10 = add(10) 
// assigns to 'add10' (num2) => (num3) => num1 + num2 + num3 
// -> closure1 {num1: 10}
const add10plus20 = add10(20) 
// assigns to 'add10plus20' (num3) => num1 + num2 + num3 
// -> closure1 {num2: 20} -> closure2 {num1: 10}

// looks up the closures to source num1 and num2
add10and20(30) // 10 + 20 + 30 --> 60

Finally and Next

Hopefully this gives a bit of an insight into currying. In the next article I will be looking into a couple of methods for automating the process of creating curried functions for us.

const curriedAdd = curry((number1, number2) => number1 + number2)
const add10 = curriedAdd(10)
add10(20) // 30