Introduction
Composition is one of those things that you hear about a lot in functional programming. In this article, we will learn about composition and how to use it.
Composition
Composition is a function that takes multiple functions that are unary and returns a function that is the collective result of the functions passed to it. The functions passed to the composition function are executed from right to left.
In calculus, we have would have come across function composition as
f(g(x)) = (f ∘ g)(x)
In JavaScript, we can write the same thing using compose as
f(g(x)) = compose(f, g)(x)
Let's take a look at an example.
let square = (x) => x * x;
let triple = (x) => x * 3;
let compose = (f, g) => (x) => f(g(x));
let squareAndTriple = compose(square, triple);
square(triple(2)); // 36
squareAndTriple(2); // 36
We can see that the function squareAndTriple
is the composition of the functions square
and triple
. The function squareAndTriple
takes one argument, x
, and passes it to the function triple
. The result of the function triple
is then passed to the function square
. The result of the function square
is returned.
This concept of combining multiple functions into a single function is called composition.
Pipe vs Compose
Pipe and compose are two different ways of combining functions but only differ in the order in which the functions are executed. In pipe, the functions are executed from left to right whereas in compose, the functions are executed from right to left.
Implementation
Let's take a look at how we can implement these functions in JavaScript.
Compose
function compose(...fns) {
return function composed(v) {
for (let fn of fns.reverse()) {
v = fn(v);
}
return v;
};
}
Using ES6, higher-order functions
let compose =
(...fns) =>
(x) =>
fns.reduceRight((v, f) => f(v), x);
Pipe
function pipe(...fns) {
return function piped(v) {
for (let fn of fns) {
v = fn(v);
}
return v;
};
}
Using ES6, higher-order functions
let pipe =
(...fns) =>
(x) =>
fns.reduce((v, f) => f(v), x);
You don't have to write your own compose and pipe functions. You can use any functional programming library like lodash or Ramda.
Currying in Composition
Currying which is another technique that is used in functional programming is very useful when composing functions. Let's take a look at an example.
let square = (x) => x * x;
let multiply = (x, y) => x * y;
let sum = (x, y) => x + y;
multiply(2, square(sum(2, 3))); // 25
sum = _.curry(sum);
multiply = _.curry(multiply);
compose(multiply(2), square, sum(2))(3); // 25
We know that for composition to work, the functions passed to it must be unary. In the example above, the function sum
and multiply
are not unary and cannot be composed. So, we use currying and provided one input to each thereby producing two unary functions that could be composed together.
Note: Composition is associative.
f((g ∘ h)(x)) = (f ∘ g)(h(x))
This is the reason why we are able to use currying in composition
Conclusion
Hopefully, you now have a better understanding of composition and how to use it. If you have any questions or suggestions, please leave a comment below.