Understanding Composition in JavaScript

Understanding Composition in JavaScript

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.