Understanding FP currying in JavaScript

Article cover imageSlow ThinkerMar 12, 2023

Article cover image

The point of this article is to share my recent experience of re-learning the basics of FP (functional programming) and making sense of the technique called currying which throughout my career was most associated with FP.

Below is a very simple example for building intuition about currying, it might not seem much but it can take a while to use it effectively.

const uncurriedAdd3 = (x, y, z) => x + y + z;
console.log(uncurriedAdd3(2, 2, 2)); // 6

const curriedAdd3 = x => y => z => x + y + z;
console.log(curriedAdd3(2)(2)(2)); // 6

const anotherCurriedAdd3 = (x, y) => z => x + y + z;
console.log(anotherCurriedAdd3(2, 2)(2)); // 6

To understand currying in JavaScript, one must first understand how closures work and the concept of higher order functions.

Closures and higher order functions

I would describe higher order functions as functions that take in other functions as arguments or return functions, or both.
Closures are a way of storing internal state for nested functions (higher order), the best way I can explain it is by using a code example.

// adder returns a function so it can be considered a higher order function
const adder = () => {
	let sum = 0;

	return (a) => {
		sum += a;

		return sum;
	};
};

// sum is internally stored and initialized at 0
const add = adder();

// sum increases with each call
add(1); // 1
add(1); // 2
add(2); // 4

The adder closes over the sum and returns a new function that increases the value of sum and returns the new value. If the adder is changed to receive the sum as an argument it will still behave as a closure, with the added benefit that the initial value of sum need not be hardcoded.

const adder = (sum) => {
	return (a) => {
		sum += a;

		return sum;
	};
};

adder can be refactored to be a bit tighter.

const adder = sum => a => sum += a;

const add = adder(0);

add(1); // 1
add(1); // 2
add(2); // 4

Now adder looks suspiciously similar to the curried add example in the beginning.

Currying

  • explaining benefits
  • how closures and partial application fit together
  • more examples of currying

The technique of currying is about writing a function that has multiple arguments as a sequence of nested functions that usually take in only one argument. As far as I'm aware there's no hard rule that says that having a sequence of nested functions with more than one argument is not currying.
The term usually used for the number of arguments a function takes in is arity. A function with an arity of one is called a unary function.
When currying a function, the process of nesting a chain of functions is basically leveraging the concept of closure to store the parameters previously passed so they are available for the subsequent function calls.

const uncurriedAdd3 = (x, y, z) => x + y + z;
uncurriedAdd3(1, 2, 3); // 6

const curriedAdd3 = x => y => z => x + y + z;
curriedAdd3(1)(2)(3); // 6

const curriedAdd2 = curriedAdd3(0);
curriedAdd2(2)(2); // 4

const increment = curriedAdd2(1);
[0, 1, 2].map(increment); // [1, 2, 3]

Currying provides flexibility and can increase code reuse if properly laid out. Granted, the example above is simplistic, I still think it provides a glimpse of what can be achieved with this technique.

I feel like I need to mention another possibility for storing function argument values, called partial application. Partial application is the process of providing a function with less parameters than it needs in order to store them for subsequent calls. .bind can be used for partially applying parameters in advance and obtain a new function with a smaller arity.

const add = (x, y) => x + y;

// partially applied add
const add2 = add.bind(null, 2);

// add2 is unary
add2(2) // 4

There are some subtle differences between Currying and Partial application. For example, .bind on a function with higher arity returns a function with lower arity so no need for chaining nested functions like previous examples of currying.

In the process of learning I've got the feeling that currying is usually preferred, at least in JavaScript. To get the full advantage people use functional libraries like Ramda to be able to write high arity functions and use Ramda's curry utility to generate the curried version.

const uncurriedAdd3 = (x, y, z) => x + y + z;
uncurriedAdd3(1, 2, 3); // 6

const curriedAdd3 = R.curry(uncurriedAdd3);
curriedAdd3(1)(2)(3); // 6

const curriedAdd2 = curriedAdd3(0);
curriedAdd2(2)(2); // 4

const increment = curriedAdd2(1);
[0, 1, 2].map(increment); // [1, 2, 3]

Deeper dive

In order to see currying in action, or to at least get some indication of how it might be used in building a somewhat trivial program, I've prepared a checklist application where I've tried to apply what I've learned about FP until now.
I will probably learn better FP practices in the future and improve on this example, but for now this program is written at my current level of skill and knowledge. I wouldn't say this is currently production grade code, but I am joyful in sharing it nonetheless.

Takeaways

My experience mostly consists of building custom software for clients. There was never a major push towards FP from outside the team, so not many have bothered to go out of their comfort zone too much.

Personally, I wanted to learn more about FP, so I went about doing that in a unstructured way. By unstructured I mean just reading a few introductory articles between working on my other tasks and watching a few videos to get the gist of it.
Now I'm attempting to consolidate my knowledge by going at it in a more focused way and documenting what I've learned.

I am making an effort to avoid inaccuracies in these articles but I am also trying to share the knowledge the way I currently understand it and from my own perspective so it might not be perfect. Nonetheless, I believe it's useful to research from multiple sources and diversified points of view to be able to make up one's own mind and own genuine interpretation.