Use function composition in JavaScript
Prerequisite: I use currying in this post, so if you don't know about that, I encourage you to read my previous post: Currying in Javascript
What is function composition?
Function composition is a mechanism of combining multiple simple functions to build a more complicated one. The result of each function is passed to the next one. In mathematics, we often write something like: f(g(x))
. So this is the result of g(x)
that is passed to f
. In programing we can achieved the composition by writing something similar. Let's take a quick example. Suppose I need to make some arithmetic by doing the following operation: 2 + 3 * 5
. As you may know, the multiplication has the priority over the addition. So you start by calculating 3 * 5
and then when add 2
to the result. Let's write this in JavaScript. The primary and certainly the most simple approach could be:
const add = (a, b) => a + b;
const mult = (a, b) => a * b;
add(2, mult(3, 5))
This is a form of function composition since this is the result of the multiplication that is passed to the add
function. Let's go a step further and see another case where function composition can be very useful. Suppose now, that I have a list of users and I need to extract the name of all the adult users. I would personaly write something like:
const users = [
{ name: "Jeff", age: 14 },
{ name: "Jack", age: 18 },
{ name: "Milady", age: 22 },
]
const filter = (cb, arr) => arr.filter(cb);
const map = (cb, arr) => arr.map(cb);
map(u => u.name, filter(u => u.age >= 18, users)); //["Jack", "Milady"]
It is good, but it could be better if we were able to automate the composition. At least it could be more readable.
Automate the function composition
So our goal in this section is to create a high order function that take two or more functions and compose them. So let's define our final signature of our future function:
compose(function1, function2, ... , functionN): Function
So for example we'd want to call the function like this:
compose(add1, add2)(3) //6
So the implementation of such a function could be:
const compose = (...functions) => args => functions.reduceRight((arg, fn) => fn(arg), args);
Isn't that awesome? This only one line function allows you to compose any function to build complex transformation. Let me explain what happens here:
compose
is a high order function. It is a function that returns another function.compose
takes multiple functions as arguments and we convert them into an array of functions using the spread opeartor:...
- Then we simply apply a
reduceRight
on those functions. The first parameter of the callback is the current argument. The second argument is the current function. Then we call each function with the current argument and the result is use for the next call.
Now we can use this function on our previous example. I volontarly currified the map and filter functions so it is more readable:
const filter = cb => arr => arr.filter(cb);
const map = cb => arr => arr.map(cb);
compose(
map(u => u.name),
filter(u => u.age >= 18)
)(users) //["Jack", "Milady"]
I propose to you a last example. Let's implement the traditional MapReduce.
MapReduce with function composition
The principle of MapReduce is simple. It is just applying a map on a set of data and reduce the result to produce a single result. This is typically the principle of function composition. So for example, we can implement the traditionnal word counter to count a number of words for example. The map will be responsible for just sending 1 when it encounters a value and the reduce will sum up the final array to produce the result:
const reduce = cb => arr => arr.reduce(cb); //Just currify the reduce function
const mapWords = map(() => 1);
const reduceWords = reduce((acc, curr) => acc += curr)(0)
compose(reduceWords, mapWords)(['foo', 'bar', 'baz']); //3
Pipe or composition?
I addded this part since Yeiber Cano mentionned that I first implemented pipe
instead of compose
. You can read his comment just below this article.
So the main difference between compose
and pipe
is the order of the composition. Compose performs a right-to-left function composition since Pipe performs a left-to-right composition. So let's write the pipe
high-order function:
const pipe = (...functions) => args => functions.reduce((arg, fn) => fn(arg), args);
So in this case, we use reduce
instead of reduceRight
to perform the composition from left to right.
We can then apply our newly created function to our previous examples:
pipe(
filter(u => u.age >= 18),
map(u => u.name),
)(users) //["Jack", "Milady"]
pipe(mapWords, reduceWords)(['foo', 'bar', 'baz']);
Some people prefer using pipe
over compose
because they find it more readable. At least, we can all agree that it is more natural!
Conclusion
This is a trivial example here, but be aware that you can use it on more complicated ones. The compose function is implemented on the most functional library like lodash or ramda. You can even find variant on function composition. For example Ramda propose a composeP
function that allows you to compose functions that return promises: http://ramdajs.com/docs/#composeP
Your Post helped me a lot , its a great to have such insight of how to use composition in many possible scenarios with pure functional programming and modular design, thanks for sharing !
Hey guys! Could you suggest me some literature to read about Function Composition? Thanks in advance!
There’s basically an entire study dedicated to this!
https://github.com/hmemcpy/milewski-ctfp-pdf
I thought Maybe shouldn’t execute the function and pass 0 to your reduceWords?
If you do that will cause “TypeError: arr.reduce is not a function”.
I think, he made a mistype.
It should was: