Making Curry: JavaScript Functional Programming
Tags: javascript, programming, tutorial, development
Published: 2023-10-30 21:43:44

Let's build a functional programming mainstay: the curry function. We will work through identifying, writing, troubleshooting, and improving a curry function.

## Getting Started

### Code Examples

Most code examples can be run in isolation, though some expect definitions from a previous block to be available.

I use the pattern of a comment following the line to represent the output of that code.

```javascript
// Example of operation and output
2 + 2;
// 4
```

If you would like to follow along or try the code examples for yourself, I strongly recommend using [RunJS](https://runjs.app).

## What is Currying

Currying is an important part of functional programming, and a great example of [higher-order functions](https://developer.mozilla.org/en-US/docs/Glossary/First-class_Function#passing_a_function_as_an_argument). 

Broadly speaking, currying creates a collector or _accumulator_ of function arguments. A curried function returns another accumulator until it has all of the arguments needed to execute the function. Currying is a specific form of [partial application](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind#partially_applied_functions).

Here is a simple example of the difference between regular and curried function calls.

```javascript
// Regular example
let addThree = (a, b, c) => a + b + c;

addThree(1, 2, 3);

// Now, curried! Just an example, no curry function yet.
addThree = curry((a, b, c) => a + b + c);

addThree(1)(2)(3);
addThree(1, 2)(3);
addThree(1)(2, 3);
addThree(1, 2, 3);
```

Some strict curry implementations may allow only one argument to be passed at a time. This can be useful, and we will discuss it later, but we aren't going to hold to that limitation.

### Breaking it Down

What happens when we curry and then execute a function roughly works out to several steps:

1. We pass a function to `curry`.
2. Curry counts the number of arguments the function expects.
3. Curry returns an accumulator function.
4. We call the accumulator with some or all of the arguments.
5. Curry returns an accumulator until all of the expected arguments of the original function are provided.
6. When all the arguments are provided, curry executes the original function with the arguments.

Now that we have steps, we can try to create this behavior for ourselves. We will build some simple versions and improve upon them, trying to explain out thoughts and limitations along the way.

## Getting Started: Manual Currying

First, let's build the most basic form: manual currying. Just as a proof-of-concept, we're going to make a function that adds two numbers, and then a curried version.

```javascript
// Almost as simple as it gets
const add = (a, b) => a + b;
add(1, 2);
// 3

// Manually curried with two arrow functions
const curriedAdd = (a) => (b) => a + b;
add(1)(2);
// 3
```

If you are not used to working with higher-order functions, that curried version may be difficult to read, so let's talk it through.

> `curriedAdd` is a function which takes the argument `a` and returns a second function. The second function takes the argument `b` and returns `a` plus `b`.

The second function still has access to the first argument thanks to [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures), so we can access both to complete the work.

If you are still unsure about the nested functions, here are a few more ways to write it that may help.

```javascript
// With extra parentheses
const curriedAdd2 = (a) => ((b) => a + b);

// With braces
const curriedAdd3 = (a) => {
  return (b) => a + b;
};

// With old-school functions
const curriedAdd4 = function (a) {
  return function (b) {
    return a + b;
  };
};
```

Hopefully one or more of the examples helped clarify the function-in-function behavior.

## Real Life Currying

The examples above don't show how we would actually use currying. Let's take a look at why it might be useful.

Currying lets us split ownership of different arguments to make reusable functions.

```javascript
// Some generic data
const distances = [ 1, 2, 4, 5, 8 ];

// Our curried multiply function
const multiply = curry((a, b) => a * b);

// Find the first value somewhere.
const factor = getConversion('mile', 'km');
// 1.6
const convertMileToKilometer = multiply(factor);

const newDistances = distances.map(convertMileToKilometer);
// [ 1.6, 3.2, 6.4, 8, 12.8 ]
```

We can also see this being useful if we have to fetch parameters asynchronously.

```javascript
// Some generic data
const distances = [ 1, 2, 4, 5, 8 ];

// Our curried add function
const multiply = curry((a, b) => a * b);

fetchConversionFactor('mile', 'km')
  .then((factor) => multiply(factor))
  .then(converter => distances.map(converter));
  
// Just for giggles, we can avoid the extra arrow functions

fetchConversionFactor('mile', 'km')
  .then(multiply)
  .then(distances.map.bind(distances));
```

When values or operations come from different places, currying can allow different "owners" to contribute to code without the end consumer needing to know about the steps along the way.

## Arity
Before we dive in, we need to learn a new term: arity.
Arity is the number of arguments a function accepts. This can be found by accessing the `.length` property on a function.

[There are some limitations](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/length#using_function_length) because `length` does not include rest parameters and stops at the first default value. Here are some quick examples:

```javascript
const example1 = (a, b) => a + b;
example1;
// 2

const example2 = (a, b = 1) => a + b;
example2.length;
// 1 - Stop at the default value

const example3 = (a, b = 1, c) => a + b + c;
example3.length;
// 1 - Stop at the default value

const example4 = (a, b, ...c) => a + b + sum(c);
example4.length;
// 2 - Do not include rest parameters

const example5 = (...a) => sum(a);
example5.length;
// 0 - No regular parameters
```

Arity is important to making a proper curry function because we need to know when to stop accepting arguments and run the curried function.

## Starting a Curry

Back in the What is Currying section, we identified several step that happen when currying. Let's see those again.

1. We pass a function to `curry`.
2. Curry counts the number of arguments the function expects.
3. Curry returns an accumulator function.
4. We call the accumulator with some or all of the arguments.
5. Curry returns an accumulator until all of the expected arguments of the original function are provided.
6. When all the arguments are provided curry executes the original function with the arguments.

We can use a simple currying example to test our progress:

```javascript
const add = (a, b) => a + b;
const curriedAdd = curry(add);
curriedAdd(1)(2);
```

### 1. We pass a function to `curry`.

```javascript
const curry = (fn) => fn;

// Test it!
const add = (a, b) => a + b;
const curriedAdd = curry(add);
curriedAdd(1)(2);
// TypeError: curriedAdd(...) is not a function
```

Not so good, yet. The second function call doesn't work.

### 2. Curry counts the number of arguments the function expects.

```javascript
const curry = (fn) => {
  const arity = fn.length;
  return fn;
};

// Test it!
const add = (a, b) => a + b;
const curriedAdd = curry(add);
curriedAdd(1)(2);
// TypeError: curriedAdd(...) is not a function
```

We counted, but didn't make any other change, so we need more.

### 3. Curry returns an accumulator function.

```javascript
const curry = (fn) => {
  const arity = fn.length;
  const previousArgs = [];
  const accumulator = (arg) => {
    // Keep track of the arguments.
    previousArgs.push(arg);
  };
  return accumulator;
};

// Test it!
const add = (a, b) => a + b;
const curriedAdd = curry(add);
curriedAdd(1)(2);
// TypeError: curriedAdd(...) is not a function
```

We're building up, but now we don't actually run the function, and we still have a type error for that second call.

### 4. We call the accumulator with some or all of the arguments.

This is actually already covered by our tests in the previous step, so no new code for this one.

### 5. Curry returns an accumulator until all of the expected arguments of the original function are provided.

```javascript
const curry = (fn) => {
  const arity = fn.length;
  const previousArgs = [];
  const accumulator = (arg) => {
    previousArgs.push(arg);
    if (previousArgs.length < arity) return accumulator;
  };
  return accumulator;
};

// Test it!
const add = (a, b) => a + b;
const curriedAdd = curry(add);
curriedAdd(1)(2);
// undefined
```

Well, we aren't throwing an error anymore, but we aren't running the original function, either. Our code should probably do that at some point...

### 6. When all the arguments are provided curry executes the original function with the arguments.

That's what we needed!

```javascript
const curry = (fn) => {
  const arity = fn.length;
  const previousArgs = [];
  const accumulator = (arg) => {
    previousArgs.push(arg);
    if (previousArgs.length < arity) return accumulator;
    // Run the function when we have enough arguments
    return fn(...previousArgs);
  };
  return accumulator;
};

// Test it!
const add = (a, b) => a + b;
const curriedAdd = curry(add);
curriedAdd(1)(2);
// 3
```

We did it!

## Testing Capabilities

We've done a simple two-argument curry, but there are some limitations to our design. Let's revisit the `addThree` example at the beginning and see how we do:

```javascript
addThree = curry((a, b, c) => a + b + c);

addThree(1)(2)(3);
// 6

addThree(7,8,9);
// 6

addThree(1, 2)(3);
// TypeError: addThree(...) is not a function
```

I'm _pretty sure_ `7 + 8 + 9` is not six, and we get errors when we try to pass arguments in more than one call, so we need to figure out what happened.

### Don't Share Closures

It turns out we made a terrible mistake! We kept a single list of arguments in an outer closure, so we share a closure for all uses of the function. This means we can only use it _one time_, and if we tried to pass partial arguments to it several times, it would behave incorrectly.

```javascript
const add = curry((a, b) => a + b);

// Try to make two different functions
const addTwo = add(2);
// ƒ accumulator()

// This doesn't return a function because of the shared accumulator.
const addThree = add(3);
// 5
```

This is sort of the _opposite_ of currying. Looking back at What is Currying section, there is some key language that describes the problem:

> A curried function **returns another accumulator** until it has all of the arguments...

Each step of the currying process needs to have a unique set of arguments, so each step creates a reusable function. To do this, we'll need to pass our collection of arguments to each successive accumulator.

We could go about this a couple of ways... we could use `Function.prototype.bind()` to set the first value, or we could use an another arrow function to provide a closure. I'm going with the arrow function to avoid setting a context.

```javascript
const curry = (fn) => {
  const arity = fn.length;
  const accumulator = (previousArgs) => (arg) => {
    const newArgs = [...previousArgs, arg];
    if (newArgs.length < arity) return accumulator(newArgs);
    // Run the function when we have enough arguments
    return fn(...newArgs);
  };
  // Start with no arguments passed.
  return accumulator([]);
};
```

Now, each time we call the accumulator we first pass in the previous arguments and get back a function that takes the _next_ argument. You can see our first call to accumulator passes an empty array. If we didn't do this, it would cause an error when spreading `previousArgs`. We could also have used a default parameter, if we wanted.

Let's see how this version works.

```javascript
addThree(1)(2)(3); // 6
addThree(1, 2)(3); // ƒ ()
addThree(1)(2, 3); // ƒ ()
addThree(1, 2, 3); // ƒ ()
```

No errors anymore, but we aren't successfully processing versions that pass more than one argument at a time. But that's an easy fix. We need to take `...args` instead of `arg`.

### Accept Multiple Arguments

I mentioned before that strict versions of curry accept only one argument at a time. This can be useful when you need to use the function in a place where you don't want all the arguments passed to it, like in `Array.prototype.map()`. Because the mapper receives _three_ arguments – value, index, and array – it would _misbehave_ if curry accepts multiple values.

```javascript
const multipliers = [2, 4, 6, 8];
const multiply = curry((a, b) => a * b);

const multiplierFns = multipliers.map(multiply);

// With one argument at a time:
// [ ƒ (), ƒ (), ƒ (), ƒ () ]

// Supporting multiple arguments:
// [ 0, 4, 12, 24 ]
```

While this might seem like we should only accept one argument at a time, our code is more flexible for all the other times. The Array prototype function issue is well-known, and a common example is seeing `.map(parseInt)` misbehave. For situations where we know we have a concern, like `.map()`, we can create a simple unary function wrapper to prevent the error.

```javascript
const multipliers = [2, 4, 6, 8];
const multiply = curry((a, b) => a * b);

// Only one argument, no matter how curry is written
const multiplierFns = multipliers.map((value) => multiply(value));
// [ ƒ (), ƒ (), ƒ (), ƒ () ]
```

It's a pretty good pattern. It allows `curry` to be flexible and you put the fix for "bad arguments" in the place where they happen. This is common enough that libraries like Lodash include a [unary function](https://lodash.com/docs#unary).

So, let's not worry about those cases, and move forward with `...args`.

```javascript
const curry = (fn) => {
  const arity = fn.length;
  // Accept more than one argument at a time!
  const accumulator = (previousArgs) => (...args) => {
    const newArgs = [...previousArgs, ...args];
    if (newArgs.length < arity) return accumulator(newArgs);
    // Run the function when we have enough arguments
    return fn(...newArgs);
  };
  // Start with no arguments passed.
  return accumulator([]);
};
```
And some quick testing:

```javascript
addThree(1)(2)(3); // 6
addThree(1, 2)(3); // 6
addThree(1)(2, 3); // 6
addThree(1, 2, 3); // 6
```

Now our various argument styles seem to be working as expected. To be sure, we should test the reusability of our functions, which was a problem with our early closure design.

```javascript
const add = (a,b) => a + b;
const curriedAdd = curry(add);

const addTwo = curriedAdd(2);

addTwo(10); // 12
addTwo(5); // 7
addTwo(-2); // 0
```

Looking good!

### Setting Arity

Right now our curry function depends on the `length` property of the function, but as we mentioned when we learned about arity, there are reasons it can be "wrong", or at least different from what we need.

```javascript
const addAll = (...args) => args.reduce((a, b) => a + b, 0);
addAll.length; // 0

const multiply = (a, b = -1) => a * b;
multiply.length; // 1
```

For cases like these, we need to set the arity for currying to work correctly. It's easy enough to add an optional second argument to the curry function, and we can leverage how [default parameters have access to the earlier parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters#earlier_parameters_are_available_to_later_default_parameters) to add this with _less code_ than before by moving `arity` into the arguments.

```javascript
const curry = (fn, arity = fn.length) => {
  // Accept more than one argument at a time!
  const accumulator = (previousArgs) => (...args) => {
    const newArgs = [...previousArgs, ...args];
    if (newArgs.length < arity) return accumulator(newArgs);
    // Run the function when we have enough arguments
    return fn(...newArgs);
  };
  // Start with no arguments passed.
  return accumulator([]);
};

const add = (...args) => args.reduce((a, b) => a + b, 0);
curry(add, 4)(1)(2)(3)(4); // 10
curry(add, 2)(10, 5); // 15
```

## Other Considerations?

### Context

Context can be a complex and annoying part of JavaScript, but we might need to add the ability to accept a context. We could pass the context at curry time, but if we know the context at that point, we can just pass a bound function to curry in the first place. [Bind passes along the function length](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind), so we don't have to make any changes to our existing curry function to support that.

```javascript
curry(doSomethingWithContext.bind(this));
```

If we need a dynamic context, it will be the context called _last_. For this to work we cannot use all arrow functions, as they use the context in which they were declared. Only the one function that actually receives new arguments needs to be changed, though.

```javascript
const curry = (fn, arity = fn.length) => {
  // Accept more than one argument at a time!
  const accumulator = (previousArgs) => function (...args) {
    const newArgs = [...previousArgs, ...args];
    if (newArgs.length < arity) return accumulator(newArgs);
    // Run with context when we have enough arguments
    return fn.apply(this, newArgs);
  };
  // Start with no arguments passed.
  return accumulator([]);
};
```

Now, a very contrived example for testing. 

```javascript

const makeMessage = function(quantity, color) {
  return `Find ${quantity} ${color} ${this.noun}!`;
};

const requestColor = curry(makeMessage)(4);

const myContext = {
  noun: 'airplanes',
  requestColor,
};

myContext.requestColor('blue'); // "Find 4 blue airplanes!"
```

I included this in "Other Considerations" because it blends in parts of Object-Oriented Programming that we mostly try to avoid in Functional Programming. Adding support for context is simple enough and can enable the use of curried functionality inside OOP, but I try to avoid blending them together this way when I can help it.

### Bad Types

Our curry doesn't do any validation that you passed it a function or that the arity is a number. Both of these could cause errors or unexpected behavior. If we are planning to put this curry function into production it might be worth that additional sanity check.

### Empty Arguments

If you need to support an implicit `undefined` value, this version won't work for you.

```javascript
const sum = (...args) => args.reduce((a,b) => a + b, 0);
const currySum = curry(sum, 4);

// Because we use ...args, we get an empty array.
// If we used the single arg, it would be undefined.
currySum()(1, 2)()(3, 4); // 10
```

You can pass an _explicit_ `undefined` as an argument, just not an implicit one. If you undo the work from the "Accept Multiple Arguments" section, your curry will accept implicit undefined, but of course loses the ability to pass multiple arguments at a time.

### Too Many Arguments

Right now our curry function accepts more arguments than the arity.

```javascript
const add = (...args) => args.reduce((a,b) => a + b, 0);
const addFour = curry(add, 4);

// What if I pass more than four?
addFour(1, 2, 3, 4, 5); // 15
```

Both of these considerations are consistent with the behavior of the Lodash curry function, so I'm not calling them errors, merely _considerations_. For use cases where an implicit `undefined` or the exact number of arguments is required, it might be better to use a curry function which only accepts one argument at a time. If we switch back to using `arg` instead of `...args`, both of these behaviors change.

## Closing Remarks

I'm sure there are other issues and edge cases which aren't handled here, but I think our curry function works for most purposes. If something is missing, please comment and let me know!

It's been a long journey, but hopefully it was worth it to think through the design and decisions together. Thanks for reading!