← Go back

Understanding Function Composition in JavaScript

· 3 min read

An integral part of Functional Programming  -  Function Composition is a method of assembling multiple functions to create a new one.

Example of composing atomic functions ("join", "reverse", "split") to create a new function ("reverseText"), which reverses a text.


Getting Started

Theoretical part

In short, when you compose multiple functions you get another function, which when called with some arguments, in turn, calls underlying functions sequentially and passes their output as input to subsequent ones until we get the final output.

Practical part

Let’s take a look at a real implementation:

/* ======= IMPLEMENTATION ========*/
function compose(...fns) {
  return function(...args) {
    return fns.reduceRight((result, fn) => [fn.apply(null, result)], args)[0]
  }
}

/* ======= USAGE ================= */
const reverse = items => items.reverse()
const join = items => items.join(' ')
const split = items => items.split(' ')

const reverseTxt = compose(join, reverse, split);
reverseTxt('inheritance over composition'); // composition over inheritance

/* ======= USAGE with Currying ================= */
const reverse = items => items.reverse()
const join = curry((token, items) => items.join(token))
const split = curry((token, items) => items.split(token))

const reverseTxt = compose(join, reverse(' '), split(' '));
reverseTxt('inheritance over composition'); // composition over inheritance

If you’re not familiar with currying, feel free to read ”The Simplest Intro to Currying in JavaScript” article first.

A few things to note here:

  • compose(...fns) - we are using the rest parameters JS feature in order to support as many functions as passed to us.
  • function(...args) - similar to the previous point, here we’re also using rest parameters, which allows us to be initial parameter length agnostic.
  • fns.reducerRight-we are using Array.prototype.reduceRight in order to call functions from last to first and accumulate the final result. Also note, that we are accumulating an array with a single value [fn.apply(null, result)] because of the initial value args being an array, which in turn forces us to use [0] index when returning the final result.

NOTE: By convention compose works from right-to-left. It is also possible to create and use the left-to-right version too, though it will be usually called differently like pipe or sequence.

Debugging

Sometimes we may struggle to understand where exactly something is going wrong and we may want to check what is happening after each function call:

  • Obviously, we can use a built-in IDE debugger
  • Or, we also can have some dedicated helper function that simply will log data at a certain point
/* Impure helper function */
const trace = tag => value => {
  console.log(`tag: ${value}`)
  return value
}

const reverseText = compose(
  join,
  trace('after reverse'), // after reverse: ['composition', 'over', 'inheritence']
  reverse,
  split
)

reverseText('inheritence over composition') // composition over inheritance

NOTE: So as you might probably already have guessed the order of functions matters, since they are called sequentially.

Testing

Also, you might have heard how Functional Programming encourages you to use pure functions and avoid creating impure functions with unpredictable side effects.

The main reasons are:

  • The pure functions are easier to understand and test
  • Composing pure functions gives you another pure function, on which the above point is also applicable

Summary

Function Composition is a very powerful technique, which allows us built more complex functions using smaller atomic ones.

It’s comparable to a lego when you build more advanced constructions using smaller building blocks.

Hopefully, now you have a better understanding of Function Composition, which is one of the core features of Functional Programming, together with other techniques like Currying.

— P.S: If you find this article interesting, feel free to follow me on Medium as well as on Twitter.

© 2022 Erzhan Torokulov