DEV Community

John
John

Posted on • Updated on

Function composition and the making of compose

lego blocks
Image by StockSnap from Pixabay

Who is this for?

Any one who is interested in functional programming in JavaScript and would like to understand how a compose function may be derived and all for the sake of greater understanding. It is not intended for the experienced fp programmer. The compose function that we will make here is not even intended for production use.

Intro

In this post I will take the following steps

  • show the normal use case for Array.prototype.reduce
  • show how we can .reduce an array of functions
  • make a generic compose function from a decoupled reduce function
  • test this compose on a simple mathematical problem
  • finally, use it to solve the shopping cart example in TK's post, see below

As I would like to keep this post as short as possible, I would recommend that if you are unfamiliar with any of the following terms: first class functions, higher order functions, pure functions, currying, partial application, immutability and folding, then please read these excellent posts here on DEV...

Additionally, please read this post by Joel Thoms on method decoupling on hackernoon.com.

So now you know what is to come, the rest is in the detail.

A simple start

Here is the normal use case for reduce in JS


const  sum = (a, b) => a + b

const  sumIntegers = [1,2,3,4,5].reduce(sum, 0)
// => 15

Here we are summing the list of integers. In code order we assign to sumIntegers the result of an array of integers applied to the reduce method with the callback function, or "reducer", sum; finally we must supply an empty or identity value as the last argument to reduce. It is important to understand that the identity value has a relationship with respect to the reducer it serves. For the sum reducer, identity is 0:

0 + (a number) = that number
ie.  0 + 1 = 1,
and 0 + 2 = 2

for multiplication the identity value is 1:

1 x (a number) = that number
ie. 1 x 2 = 2
and 1 x 4 = 4

This means that the identity value has no effect on the first iteration of the reduction.

Let's ramp it up a notch

In JS, functions are first class. They can be essentially treated like any other data type and can be reduced or folded, just like the list of integers above; this in fp, when applied to functions, is what is termed function composition.

But, if we are going to use reduce on an array of functions then what will be our reducer and what will be it's associated identity value? Well, it turns out that, they both must be functions, because we are reducing functions. This reducer is commonly referred to as composeB, while the empty value is called Identity. Incidentally, these as part of a group of functions are called combinators. Take a look at this github page of common combinators, the B or composeB combinator is what we will be using in this next step (along with Identity). The B stands for Bluebird and originates from a book by Raymond Smullyan, a mathematician who wrote a book called To Mock a Mockingbird; a book about combinatory logic.

It should be noted that composeB is limited to accepting only two functions, whereas the compose function we will be building here will accept a list of more than two functions.

So what does composeB and Identity look like in JS? Here they are...

const  Identity  =  x  =>  x
// pass in a value and you get the same value back.

const  composeB  = (f, g) =>  x  =>  f(g(x))
// composes two functions together. Pass in a function f and a function g then apply g to data x and then run the result of that through f to get a final result.

So now we can apply these two functions to Array.prototype.reduce and just so that it can be demonstrated, let's add 5 to what ever number is passed in and then double the result.

For this we need...

const  add = a => b => a  +  b
// add is a curried function

const  add5 = add(5)
// make a new function add5

const  double = a => a * 2
// it speaks for itself

Now the thing with compose is that it reads from right to left, so ...

const add5ThenDouble  = [double, add5].reduce(composeB, Identity)

console.log(typeof  add5ThenDouble)
// => function

So, now we have a add5ThenDouble function let's apply it...

console.log(add5ThenDouble(6))

// => (6 + 5) * 2 => 22

Well it works, but wait a minute, what we actually want is a generic function called compose that will take a list of functions. For this we will need to decouple .reduce from Array.prototype like this...

const reduce = (f, i) => coll => Array.prototype.reduce.call(coll, f, i)

You can see that this is a curried function: the double =>'s tells us that. We take the reducer function and it's empty value and then the collection(an Array in JS)

Note: Notice how our functions take as their last argument the data. This is important.

So, applying this to make a generalized compose function we do...

const compose = reduce(composeB, Identity)

And there we have it, so let's do the above again using compose... remembering that this compose has been derived from reduce so it expects an array of pure functions.

const  add5_ThenDouble = compose( [double, add5] )
// Remember, it reads right to left

console.log(add5_ThenDouble(6))
// => 22

More than a Bluebird in the hand...

Now we have got our compose function let's use it to solve the last example in TK's post

Functional Programming Principles in Javascript by TK

To do this we will need, first the shoppingCart data

 let  shoppingCart  = [
  { productTitle: "Functional Programming", type: "books", amount: 10 },
  { productTitle: "Kindle", type: "eletronics", amount: 30 },
  { productTitle: "Shoes", type: "fashion", amount: 20 },
  { productTitle: "Clean Code", type: "books", amount: 60 }
]

a curry function would be nice...

const  curry = f => (...args) =>
  args.length >=  f.length
  ? f(...args)
  : curry(f.bind(undefined, ...args))

A couple of other decoupled functions, wrapped by curry.

const map = curry((fn, xs) => Array.prototype.map.call(xs, fn))
const filter = curry((fn, xs) => Array.prototype.filter.call(xs, fn))

and finally, TK's helper functions.

const sumAmount = (acc, amount) => acc + amount
const byBooks = (order) => order.type === "books"
const getAmount = (order) => order.amount

The objective of this problem is to find the sum of the amounts that has the type of 'books'.

Note: that this version of compose expects an array of functions. This is different to what you might find in a functional library like Ramda. In Ramda's compose, the functions that you want to compose are just directly used as arguments to the compose function.

const sumAmountsForBooks = compose([
  reduce(sumAmount,0),
  map(getAmount),
  filter(byBooks)
])
// reads bottom to top

console.log(sumAmountsForBooks(shoppingCart))
// => 70

So, we get the correct result, but let's see how it does with a larger dataset.

Testing, testing, testing...

// generate a test array, quick and dirty
function genTestAry(numElements) {
  let A = [], i=0, prod = ["books", "eletronics", "fashion", "diy", "health"]
  while (i < numElements) {
    A.push({productTitle:'', ['type']: prod[~~(i%5)], amount: 10})
    i++
  }
  return  A
}

// sCart with 100,000 objects.
const sCart = genTestAry(100000)

console.time('sumAmountsForBooks')
console.log("Sum for ", sCart.length, "objects:", sumAmountsForBooks(sCart))
// => Sum for 100000 objects: 200000
console.timeEnd('sumAmountsForBooks')
// reports approx 15 .. 10ms. Not bad for 100,000 items!
// As a bit of perspective; the blink of an eye is approx. 300 .. 400ms.

In summary

As an academic exercise this post has shown that the compose function can be looked at as just a reduction of a list of pure functions. The process of function composition is often likened to joining a set of Lego bricks to build a sum which is greater than its individual parts.

Of course, there is usually "more than one way to skin a cat". The shopping cart code above could be achieved in one pass, like this...

const  getTotalAmount = (shoppingCart) =>
  shoppingCart.reduce((acc, obj) =>
  obj.type === 'books'
    ? acc + obj.amount
    : acc
  ,0)

console.time('getTotalAmount')
console.log("Sum for ", sCart.length, "objects", getTotalAmount(sCart))
console.timeEnd('getTotalAmount')
// Yes, reports much faster approx. < 5ms.

Yes, this code is shorter and it uses .chaining and there is nothing wrong with that; I love dot chaining, but that would not have given me an opportunity to show how compose can do the job and how JavaScript methods can be decoupled from their parent objects. A big shout out to Joel Thoms for that. But, the upshot is, however you decide what method to use, that's fine. But function composition is another tool in your programmers' toolbox ... and that's a positive note to end on.

I hope that this post will help you to a greater understanding of function composition and that you will be tempted to explore a functional library such as Ramda or Lodash; "battle tested" functions for functional programmers. However, you may just need to compose without pulling in a library, so in vanilla JavaScript, you could just use the following.

 const compose = (...fns) => xs => fns.reduceRight((v, f) => f(v), xs)

If you are inspired to learn more, then take a peek at TK's Learning Functional Programming repository

Good luck on your fp journey!

Discussion (0)