Functional Composition in Javascript

What is ‘functional composition’?

Functional composition is when you take two or more functions, and make one a single function out of them. When you compose functions into a single function call, you can start using them as pipelines of specific behavior. These pipelines can then take the result of each function that comprises it, and use it as the argument to the next function in the pipeline. This approach of making a pipeline of functions might seem more cumbersome, but it ends up giving you more flexibility in how you work with your data, while also aiding predictability/readability/testability/etc. Another side effect of this approach, is that you can create robust pipelines that can that be composed as easily as the functions that comprise it

So how does composition work?

In it’s simplest form it’s just a function that glues two functions together. If we had two functions: add10 and add100

add10 = num => num + 10;
add100 = num => num + 100;

…we could compose them together like so…

add110 = num => add10(add100(num))

…and now we have a useful function composed out of two smaller/single-purpose functions. But what if we wanted to do something similar but different…

minus10 = num => num - 10;
minus100 = num => num - 100;

…we would have to start over from scratch in how we compose them…

minus110 = num => minus10(minus100(num))

Higher Order Functions

So we’ve built two useful functions (add110 and minus110) from smaller functions, but they repeat their main workflow. Both functions take two smaller functions and apply them in succession, which gives us an opportunity to extract the pattern from them into a higher order function called compose

 compose = (func1, func2) => arg => func2(func1(arg));

Now instead of having to manually create two separate composed functions manually, we have a higher order function to do it for us. Now we can update our add110 and minus110functions to be built using our new compose function

add110 = compose(add10, add100);
minus110 = compose(minus10, minus100);

and because it is a higher order component, it’s easy for us to reuse the base functions in unique new ways without having to hard code each case

add10Minus100 = compose(add10, minus100);
add100Minus10 = compose(add100, minus10);

Another interesting use case of this higher order function, is that because functions are a ‘first class citizen’ in javascript and able to be captured by variables, you can also compose whole new functions based off of boolean conditions

func1 = (condition < 50)? minus10: add10
func2 = (age < 20)? add100: minus100
priceAdjuster = compose(func1, func2)

Composing Multiple Functions (Arbitrary Arity)

Our higher order function is useful, but is limited to only composing two functions. What if we wanted to add any random number of functions together? That would be much more useful, but what would it look like? Surprisingly, alot like what we already wrote

compose = funcs => arg => funcs.reduce((a, b) => (arg) => b(a(arg)), i=>i)   

To understand how the above works, lets break it down into it’s main parts.

The Function Signature

compose = funcs => arg => ...

This function signature means that we are going to create a simple function that captures the funcs argument (an array of functions), and returns a new function that takes one argument called arg. This will be useful later on, because it means that we won’t have to compose all our functions and use them all at once. We can reuse or defer them for later. This capturing of the func argument is called a closure, and is used heavily in javascript. The two-step nature of this function is called currying

The Identity Function

If we really want to be able to compose a list of an arbitrary amount, we need to build in safeguards. When we do simple arithmetic like foo = 1 + 2, we expect there to be two numbers and an operator. If we were to leave off one of the numbers and just write foo = 1 +, we would get a syntax error. In the same way, we need at least one function to build off of for our compose to work, otherwise we would return null or an error.

To avoid this error, we make the base function an identity function. This is a fancy mathematical term, but for our purpose it just means ‘a function that returns what it is given’. Literally a pass-through.

i => i

This isn’t some ES6 trick, it’s literally as boring as it looks. In ES5 it would be

function(i) {
    return i
}

By basing the rest of our pipeline off of this initial function, we ensure two things:

  1. We will always return a function
  2. Our function will at a minimum return the arguments unmodified. Thus any effects are a result of the composed pipeline we built

The Reducer

So we have a way to capture our array of functions via currying, and we have our base case in the identity function, but now we need to do the meat of the work and actually compose this list of functions together. To do that, we can use javascript’s Array.prototype.reduce() method.

funcs.reduce((a, b) => (arg) => b(a(arg))

For a quick summary of what a reducer does in general, it’s comprised of 3 main parts. The array of items, a combining function to apply to them, and the running result (called an ‘accumulator’). So for a simple math example, it would look like this:

[1,2,3].reduce((accumulator, current) => {
    return accumulator + current;
})
// (((1) + 2) + 3)
// returns 6

Luckily our composition is just as simple as this math example, but it can be helpful to really expand it all the way for clarity. So this:

funcs.reduce((a, b) => (arg) => b(a(arg))

is functionally equivalent to this

funcs.reduce((composition, nextFunc) => {
    return arg => {
        const result = nextFunc(arg)
        return composition(result);
    }
})

So what we are doing with the reducer in our compose function, is building a pipeline of functions that continuously use the result of previous functions as the argument for the next one. So if we had 3 functions add100, minus10, and double, we are basically building a pipeline that expands out to

myFunc = num => double(minus10(add100(num)))

Conclusion

Functional composition can be a useful tool for applying a series of functions to some data, and is easy to use. It can aid testing and reusability by being comprised of small, single-purpose functions. It can also create more flexible code by allowing you to assemble discrete sections of transformations.

Practical Use Cases

  1. Applying a series of filters to narrow down the set of data you want in a search.
  2. Middleware to transform your redux actions once dispatched
  3. String transformations (e.g. custom slugify methods)
  4. Sorting multi-dimensional data (e.g. a set of people ordered by last name, then first when they have the same last name)