In mathematics and computer science currying is the technique of translating a function that takes multiple arguments into a sequence of families of functions, each taking a single argument.

The Pipe Analogy

One constraint of FP is that functions should only receive one input, which are technically known as unary functions. We can think of a function as a pipe that takes in one input and produces one output. This is similar to how a water pipe takes in water and produces a flow of water out the other end.

Pipe analogy

But what if we need to write a function that takes in multiple input values? We could pass all of the inputs as properties of a single object and call it a day. For example:

const greet = ({name: string, greeting: string}) => {
return `${greeting}, ${name}!`;
};
greet({name: 'John', greeting: 'Good morning'}); // => Good morning, John!

This is a perfectly valid approach, but it does introduce some potential issues that can make functions less predictable and harder to optimize in a purely functional paradigm:

Curried Functions: Building a Pipeline

The FP approach to deal with this is to create an outer function that receives a single input, wrapping an inner function (also unary). For example, let’s rewrite the previous example in a curried way:

function greet(greeting: string) {
return function(name: string) {
return `${greeting}, ${name}!`
}
}
const result = greet('Hello')('Bob')
console.log(result) // => Hello, Bob!

Note that greet is a function that returns another function. The first function takes in a single input, greeting, and returns another function that takes in a single input, name. The inner function then produces the final output. For example:

console.log(greet('Hello'))

We’re calling the only the outer function greet, this is the output:

function (name) {
return `${greeting}, ${name}!`;
}

Where’s the pipeline?

Let’s rewrite the previous example in a more functional style:

const greet = (greeting: string) => (name: string) => `${greeting}, ${name}!`;

Using arrow functions this resembles a series of pipes that take in one of the inputs and pass the output to the next pipe, creating a sort of pipeline.

Pipeline analogy

This is similar to how a water pipe can be connected to multiple other pipes, each taking in one input and producing one output. Let’s create another simple example:

type Sum = (a: number) => (b: number) => number;
const sum: Sum = (a) => (b) => a + b;
const result = sum(1)(2);
console.log(result); // => 3

You may be wondering, how this is useful? Well, imagine we want to write a function that increments a number by a certain value. We could write a function reusing our sum function:

type Increment = (value: number) => number;
const increment: Increment = (value: number) => sum(value)(1);
const result = increment(5);
console.log(result); // => 6

What about decrementing?

type Decrement = (value: number) => number;
const decrement: Decrement = (value: number) => sum(value)(-1);
const result = decrement(5);
console.log(result); // => 4

Currying Functions

Now imagine that we have a function that takes in multiple inputs, and we want to curry it so we can use it as a curried function:

// Just an usual binary function
const normalSum = (a: number, b: number) => a + b
type Curry2 = (f: (a: number, b: number) => number)
=> (a: number)
=> (b: number)
=> number
const curry2: Curry2 = f => a => b => f(a, b)
const curriedSum = curry2(normalSum)
const partialResult = curriedSum(1)
console.log(partialResult) // b => f(a, b)
const finalResult = partialResult(41)
console.log(finalResult) // 42

In the code above:

So what’s the point of currying?

  1. It allows us to create partial functions that can be reused in different contexts ().
  2. It allows us to write multi-argument functions, which is easier to reason, then turn them into their curried versions.
  3. We can use the partial functions to create other versions of the same function, like increment and decrement in the previous example.
  4. Some libraries that include multi-argument functions can be curried, and the resulting functions can be used in a more functional way.

Developers often write regular functions first and curry them later if needed. Many libraries, like Lodash, provide a _.curry function so you don’t have to manually curry everything.

A Generic Curry Function

Let’s rewrite curry2 as a generic curry function that can curry any function, regardless of the type of their arguments. Here’s an example:

type Curry2 = <T, U, V>(f: (a: A, b: B) => C)
=> (a: A)
=> (b: B)
=> C

This is a generic function that takes in a function f that takes in two arguments of type A and B, and returns a value of type C. The curried version of the function takes in the first argument of type A, and returns a function that takes in the second argument of type B, and returns a value of type C. Let’s see how this works in practice:

const normalSum = (a: number, b: number) => a + b
type Curry2 = <TA, B, C>(f: (a: A, b: B) => C)
=> (a: A)
=> (b: B)
=> C
const curry2: Curry2 = f => a => b => f(a, b)
const curriedSum = curry2(normalSum)
const partialResult = curriedSum(1)
console.log(partialResult) // b => f(a, b)
const finalResult = partialResult(41)
console.log(finalResult) // 42

This works exactly the same as before, but now we can use it with any function that takes in two arguments of any type. For example:

const normalConcat = (a: string, b: string) => a + b
const curriedConcat = curry2(normalConcat)
const partialResult = curriedConcat('Hello')
console.log(partialResult) // b => f(a, b)
const finalResult = partialResult('World')
console.log(finalResult) // HelloWorld