May 17, 2019
0 strokes bestowed

Just Enough FP: Composition

edit

Composition is the culmination of all the previous "Just Enough FP" blog posts. It's where we combine our knowledge of higher order functions, currying, partial application, and pointfree programming into a new concept that can really unlock our functional potential.

Before I get into explaining this concept, I want you to think back to your high school math classes. You might recall somewhere around the time of Algebra 1 or 2 learning about mathematical functions, like this simple one: f(x) = x + 1. This function takes any x and always returns x + 1. You might recognize this as a pure function (all mathematical functions are pure functions).

Now, let's add a second function: g(x) = x * 3. This is also a pure function. What if I wanted to take the output of one function and feed it as the input of another function? It would look like this: f(g(x)). Now, x will first be mulitplied by 3, and then 1 will be added to it. If I provide an argument of 4 for x, the result will be 13.

This is functional composition.

It is the act of taking the output of one function and passing it directly as the input to the next function. In its most primitive form, it occurs through nesting functions like you see above. Let's make a practical-ish example of this.

An example I like to use involves manipulating a string through a series of functions. Let's start by defining those functions.

const scream = str => str.toUpperCase()
const exclaim = str => `${str}!`
const repeat = str => `${str} ${str}`

The scream function uppercases a string, exclaim adds an exclamation point, and repeat, well repeats the string (with a space inbetween). You'll notice that each function has the same arity. If you've forgotten what arity is, check out the currying post before going further. Each one is a curried, unary function. Also, notice that the argument type for each function is the same. They all expect a string. Our composition wouldn't work properly if we were mixing types, but I'll save that discussion for another time. Because all of our functions expect only one more argument and have the same type signature, we can nest them together, passing the output of one as the input of the next without any worries.

repeat(exclaim(scream('Just Enough FP is great')))
// JUST ENOUGH FP IS GREAT! JUST ENOUGH FP IS GREAT!
repeat(exclaim(scream('i luv learning')))
// I LUV LEARNING! I LUV LEARNING!

That's pretty cool! But imagine you had even more things you wanted to nest, or that your function names were longer than one word. That nesting would get very verbose and difficult to read. Is there a way we can tidy this up? Of course there is.

If you recall from the currying and partial application posts, curried functions do not evaluate completely until they receive their final argument. You'll also recall that they hold their previously partially applied arguments in closure awaiting that final argument. If we were able to programmatically combine all our functions into one new higher order function that awaited its final argument, we'd have ourselves a cool way of generating compositions.

Well, here's that cool way. The compose function:

const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x)

The compose function accepts any number of functions as arguments and returns a new function that awaits any value x. When x is received, we take our array of functions and call reduceRight (the same as Array.prototype.reduce, but we start from the end and work towards the beginning of the array), passing in the accumulated result as the input to the next function, starting with our value x. compose can be used like this:

const withExuberance = compose(repeat, exclaim, scream)

withExuberance('this is a-maz-ing')
// THIS IS A-MAZ-ING! THIS IS A-MAZ-ING!

You see, we were able to generate a composition by passing in the functions from before as arguments to the compose function, which gave us a brand new function awaiting our string. This string was passed first to scream, then to exclaim, and then to repeat.

"Wait! What?!" No, I can't read your mind, it's just the response I get every time I teach this.

In our mathematical compositions, the innermost function is always called first. When we nested our functions, they read from right-to-left: repeat(exclaim(scream(x))). So compose is designed to order its functions the same way, from right-to-left (or bottom-to-top if you have so many they don't fit on one line). This takes some time to get used to, but with a little practice, becomes fairly easy to read.

We can actually break our composition down into smaller compositions and compose them together. Since we only have three functions, I'll only compose scream and exclaim.

const withExcitement = compose(exclaim, scream)
const withExuberance = compose(repeat, withExcitement)

withExuberance('composition is blowing my mind')
// COMPOSITION IS BLOWING MY MIND! COMPOSITION IS BLOWING MY MIND!

You can start to imagine how the ability to make small compositions and use them in larger ones can add some useful functionality to a program or application. So long as the arity and types match up, you should be able to compose at will without fear.

Bonus: pipe

If you ever work in a purely functional language, you'll likely become very accustomed to using compose and its right-to-left signature. However, to ease you into functional programming. There is an alternative: the pipe function.

It's exactly the same as compose, but instead of reduceRight, we use reduce.

const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x)

Our compositions now read left-to-right, top-to-bottom. Forgive me, I had to teach you composition the mathematical way first. Our pipe compositions now would look like this:

const withExcitement = pipe(scream, exclaim)
const withExuberance = pipe(withExcitement, repeat)

withExuberance("I can't think of an interesting string")
// I CAN'T THINK OF AN INTERESTING STRING! I CAN'T THINK OF AN INTERESTING STRING!

Conclusion

Composition is the act of passing the result of one function directly as the input of another function. With our knowledge of higher order functions, we are able to generate useful compositions with a compose or pipe function that await any data to operate on. It provides a mechanism for building up complex functionality through combining simpler and smaller functions.


In this series, I'm going over material from my Just Enough Functional Programming in JavaScript course on egghead. This post is based on the lesson Build Up Complex Functionality by Composing Simple Functions in JavaScript.

Sharing this article on Twitter is a great way to help me out and I really appreciate the support.
+0
Liked the post? Click the beard a few times.
Tags
Just Enough FP
Related Posts:

Let's talk some more about JavaScript, React, and software engineering.

I write a newsletter to share my thoughts and the projects I'm working on. I would love for you to join the conversation. You can unsubscribe at any time.

Just Enough Functional Programming Logo
Just Enough Functional Programming

Check out my courses!

Liked the post? You might like my video courses, too. Click the button to view this course or go to Courses for more information.
View on egghead.io
Kyle Shevlin's face, which is mostly a beard with eyes
Kyle Shevlin is a front end web developer and software engineer who specializes in JavaScript and React.