2

Why compose?

 2 years ago
source link: https://johnnyreina.com/programming/functional/2018/03/17/why-compose.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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:

const compose = (fs, x) => { let res = fs[fs.length - 1](x); for(let i = fs.length - 2;i >= 0;i--) { res = fs[i](res); } return res; } compose([x => x + 1,x => x + x], 10) // <- 21

const compose = fs => x => fs.reduceRight((memo, f) => f(memo), x);

compose([x => x + 1,x => x + x], 10) // <- 21

Awesome! We doubled a number and added 1 to it. How incredibly useful this will be! Let me stop you right there.

Identities

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?

Compose is special because it gives us an identity for function composition. If you’ve ever written a big function composition in a language that uses parenthesis for function invocation, you know damn well that it can be an abomination when you are done. “Am I writing Lisp or JavaScript?”, you ask yourself in bewilderment as you stare at the mountain of parentheses. Compose provides us with an identity between f(g(h(w(x)))) and 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:

const _formatTest = ({ id, status_id, comment = '' }) => ({ case_id: id, status_id, comment });

const _flattenAndXform = R.compose( R.map(_formatTest), R.flatten, R.pluck('tests'), R.prop('sections') );

Now let us examine the same function, without using compose:

const _formatTest = ({ id, status_id, comment = '' }) => ({ case_id: id, status_id, comment });

const _flattenAndXform = obj => R.map(_formatTest, R.flatten(R.pluck('tests', R.prop('sections', obj))));

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. Pipe and 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 pipe over compose due to this difference. I personally prefer compose, but it’s easy to understand that people would prefer left-to-right composition.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK