This will make more sense if you know what currying is. If not, don’t despair, here is an article I wrote about currying!
Compose is a function that takes some functions and gives you a new function which accepts a value and works from right to left, passing in the value to the rightmost function and the return value into the next function in the list. Here is
compose written and used in both an imperative and functional style:
Awesome! We doubled a number and added 1 to it. How incredibly useful this will be! Let me stop you right there.
Perhaps you learned about Pythagorean theorem in school,
a² + b² = c². It may seem kind of boring but this theorem is incredibly profound and the entire body of trigonometry is based on it. When you have two representations of the same thing with all information about that thing in both representations, you have an identity. Identities are great because they can greatly simply problems (or made worse) for us.
What the hell does this have to do with
Compose provides us with an identity between
compose([f, g, h, w])(x). This is neat for two reasons: 1. it retains the same function ordering and 2. we can build complex functions out of little simple ones without writing a humongous function and using up all the parentheses in our savings account.
Don’t we still have to write the functions to compose?
The answer is maybe. After using underscore and lodash for a number of years, I have taken a strong preference for Ramda. The reason for this is because all of the functions in Ramda are curried and they all accept the data as the last parameter. This is important because it allows us to apply some stuff to a utility function and have a function waiting for our data. Let’s take a look at an example from a project I recently worked on:
Now let us examine the same function, without using compose:
As you can see, a lot of work is being done here and I really only had to write one function! Not only that, but the
compose version is MUCH easier on the eyes.
The bigger picture
The fact that
compose gives us an identity for composition is cool because it gives us a firm mathematical basis for representing our compositions differently. It tells us we have a choice in how we represent our actions. For me, this is the biggest lesson to be learned from functional programming as a whole:
“it doesn’t have to be this way and I can prove it mathematically!”
One last thing
You may have heard of the
pipe function or even the “let’s all lose our minds” pipeline operator.
compose are related in the sense that they both do the same thing, except that
pipe does left-to-right composition while
compose does right-to-left composition. This may be more intuitive to some folks since the invocation order of the supplied functions is identical to the order in which they are supplied. That is,
pipe([f, g, h, w])(x) is an identity for
w(h(g(f(x)))). I have found several cases where people preferred
compose due to this difference. I personally prefer
compose, but it’s easy to understand that people would prefer left-to-right composition.