Note
The chapter examples and library source code are in branch chap07. The repo’s URL is: https://github.com/antoaravinth/functional-es6.git
Once checkout the code, please checkout branch chap07:
...
git checkout -b chap07 origin/chap07
...
For running the codes, as before run:
...
npm run playground
...
In the previous chapter we saw two important techniques for functional programming: Currying and Partial application. We discussed how these two techniques work! We also discussed that as a JavaScript programmer we will be choosing either Currying or Partial application in our code base. In this chapter we are going to see what Functional Composition means and its practical use cases.
Functional Composition is simply referred as compositionin the functional programming world. We are going to see a bit of theory on the idea of composition and quite a few examples of it. Then we will be writing our own compose function. Again, it’s fun! Understanding how compose function works under the hood is really a fun task.
Composition in General Terms
Before we see what a functional composition is all about, let’s step back and understand the idea behind composition. In this section we are going to drive a philosophy that will help us to get the benefit out of composition in general.
Unix Philosophy
Unix philosophy is a set of ideas that originated by Ken Thompson. One part of the Unix philosophy is this:
Make each program do one thing well. To do a new job, build afresh rather than complicate old programs by adding new “features.”
This is exactly what we are doing as part of creating our functions. Functions as we have seen until now in the books are supposed to take an argument and return a data. Yes, functional programming does follow Unix philoshopy. Kudos!
The second part of the philosophy is this:
Expect the output of every program to become the input to another, as yet unknown, program.
Hmmm, that's an interesting quote. What does it mean by Expect the output of every program to become the input to another? To make the point clear, let’s look at a few commands on Unix platform that were built by following these philosophies.
For example, cat is a command (or you can think of it as a function) that is used to display the contents of a text file to a console. Here the cat command takes an argument (as similar to a function), that is, the file location, etc., and returns the output (again as similar to a function) to the console. So we can do the following:
cat test.txtWhich will print to the console?
Hello worldNote
Here the content of the test.txt will be Hello world.
That’s so simple. Another command called grep allows us to search for content in a given text. An important point to note is that the grep function takes an input and gives the output (again very similar to a function).
We can do the following with the grep command :
grep 'world' test.txtwhich will return us the matching content, in this case:
Hello worldWe have seen two very simple functions: grep and cat that are is built by following the Unix philosophy. Now we can take some time to understand this quote:
Expect the output of every program to become the input to another, as yet unknown, program.
Imagine you to want to send the data from the cat command as an input to the grep command in order to do a search. We know that the cat command will return the data; and also we know that the grep command takes the data for processing the search operation. Thus, using the Unix | (pipe symbol), we can achieve our task:
cat test.txt | grep 'world'which will return the data as expected:
Hello worldNote
The symbol | is called a pipe symbol. This allows us to combine several functions to create a new function that will help us to solve our problem! Basically | sends the output of a function on the left-hand side as an input to a function on the right-hand side! This process, technically, is called s pipeline!
The above example might be trivial, but it conveys the idea behind the quote:
Expect the output of every program to become the input to another, as yet unknown, program.
As our example shows, the grep command or a function receives the output of a cat command or a function. What we have done here is that we have created a new function altogether without any effort! Of course the | pipe acts as a bridge to connect the given two commands.
Let’s change our problem statement a bit; what if we want to count the number of occurrences of the word world in a given text file? How we can achieve it?
This is how we are going to solve it:
cat test.txt | grep 'world' | wcNote
The command wc is used to count the words in a given text. This command is available on all of the Unix and Linux platforms.
which is going to return the data as we expect! As the above examples show, we are creating a new function as per our need on the fly from our base functions! In other words, we are composing a new function from our base function(s). Note that the base function needs to obey this rule:
Each base function needs to take an argument and return data!
We would be able to compose a new function with the help of |. As this chapter shows, we will be building our own compose function in JavaScript, which does the same job of | in the Unix/Linux world.
Now we have the idea of composing functions from base functions! The real advantage of composing functions is that we can combine our base function to solve the problem at hand, without re-creating a new function!
Functional Composition
In this section we are going to discuss a use case where functional composition will be useful in the JavaScript world. Stay with me – you’re going to love the idea of the compose function.
Revisiting map,filter
In Chapter 5, “Being Functional on Arrays, under the section “Chaining Operations,” we saw how to chain the data from a map and filter to solve the problem in hand. Let’s quickly revisit the problem and the solution we have taken.
We had an array of objects, whose structure looks like the following:
Listing 7-1. Apress book object structure
let apressBooks = [{"id": 111,"title": "C# 6.0","author": "ANDREW TROELSEN","rating": [4.7],"reviews": [{good : 4 , excellent : 12}]},{"id": 222,"title": "Efficient Learning Machines","author": "Rahul Khanna","rating": [4.5],"reviews": []},{"id": 333,"title": "Pro AngularJS","author": "Adam Freeman","rating": [4.0],"reviews": []},{"id": 444,"title": "Pro ASP.NET","author": "Adam Freeman","rating": [4.2],"reviews": [{good : 14 , excellent : 12}]}];
The problem was to get the title and author object out of our apressBooks for which the review is greater than 4.5. Our solution to that current problem looks like the following:
Listing 7-2. Getting author details using map
map(filter(apressBooks, (book) => book.rating[0] > 4.5),(book) => {return {title: book.title,author:book.author}})
for which we have got the result as the following:
[{title: 'C# 6.0',author: 'ANDREW TROELSEN'}]
The code to achieve the solution tells an important point. The data out of our filter function is passed into the map function as its input argument! Yeah, you have guessed it correctly. Does it sound like the exact same problem we solved in the previous section using | in the Unix world? Can we do the same thing in the JavaScript world? Can we create a function that will combine two functions by sending the output of one function as an input to another function?
Yes, we can! Meet compose function.
compose Function
In this section, let’s create our first compose function. Creating a new compose function is easy and straightforward. compose function needs to take the output of one function and give it as input to another function. Let’s put them up in in a function:
Listing 7-3. compose function definition
const compose = (a, b) =>(c) => a(b(c))
The compose function is simple and does what we need it to do. It takes two functions, a and b, and returns a function that takes the argument c. When we call the return function by supplying the value of c, it will call the function b with input of c and the output of the function b goes into as input of function a. And that’s exactly what a compose function definition is!
Great, now let’s quickly test our compose function with a simple example before we dive into our running example from the previous section.
Note
compose function executes b first and pass the return value of b as an argument to the function a. The direction of function calling in compose is right to left (i.e b executes first, followed by a).
Playing with compose function
With our compose function in place, let’s go and build some toy examples.
Imagine we want to round a given number. The given number will be a float, so we have to convert that number to a float and then call Math.round.
Without compose, we can do the following:
let data = parseFloat("3.56")let number = Math.round(data)
the output will be 4 as we would expect. As you can see in the above example, the data (which is the output of parseFloat function) is passed as input to Math.round to get a solution; this is the right problem candidate which our compose function will solve.
Let’s solve this via our compose function:
let number = compose(Math.round,parseFloat)The above statement will return a new function that is stored as a number and looks like this:
number = (c) => Math.round(parseFloat(c))Now if we pass the input c to our number function, we will get what we expect:
number("3.56")=> 4
Wow, what we have done right above is functional composition! Yes, we have composed two functions in order to build a new function on the fly! An important point to note over here is that, the functions Math.round or parseFloat aren’t executed/run until we call our number function.
Now imagine we have two functions namely:
let splitIntoSpaces = (str) => str.split(" ");let count = (array) => array.length;
Now if you want to build a new function in order to count number of words in a string, we can easily do this:
const countWords = compose(count,splitIntoSpaces);Now we can call that:
countWords("hello your reading about composition")=> 5
The newly created function countWords using compose is an elegant and easy way to see exactly what it does exactly!
curry and partial to the Rescue
We know that we can compose two functions, only if this function takes one input argument! But that’s not the case always, as there can be functions that have multiple arguments! How we are going to compose those functions? Is there something we can do about it?
Yes, we can do it using either curry or partial function that we have defined in the previous chapter! As you can recall from the section “Revisiting map,filter.” From this chapter, we have the following code to solve our problem in hand (Listing 7-2):
map(filter(apressBooks, (book) => book.rating[0] > 4.5),(book) => {return {title: book.title,author:book.author}})
Now can we use the compose function to compose both map and filter with specifics to our example? Remember that both map and filter functions do take two arguments: the first argument is the array, and the second argument being the function to operate on that array. So we can't compose these two functions directly.
However we can take help from partial functions. Remember that the above code snippet does work on the apressBooks object. Pulling it out here again for easy reference:
let apressBooks = [{"id": 111,"title": "C# 6.0","author": "ANDREW TROELSEN","rating": [4.7],"reviews": [{good : 4 , excellent : 12}]},{"id": 222,"title": "Efficient Learning Machines","author": "Rahul Khanna","rating": [4.5],"reviews": []},{"id": 333,"title": "Pro AngularJS","author": "Adam Freeman","rating": [4.0],"reviews": []},{"id": 444,"title": "Pro ASP.NET","author": "Adam Freeman","rating": [4.2],"reviews": [{good : 14 , excellent : 12}]}];
Now let’s say we have many small functions in our code base for filtering the books based out of different ratings like the following:
let filterOutStandingBooks = (book) => book.rating[0] === 5;let filterGoodBooks = (book) => book.rating[0] > 4.5;let filterBadBooks = (book) => book.rating[0] < 3.5;
and we do have many projection functions like:
let projectTitleAndAuthor = (book) => { return {title: book.title,author:book.author} }let projectAuthor = (book) => { return {author:book.author} }let projectTitle = (book) => { return {title: book.title} }
Note
You might be wondering why we have small functions even for simple things. Remember that composition is all about small functions being composed into a larger function. Simple functions are easy to read, test, and maintain; and using compose we can build anything out of it, as we will see in this section.
Now to solve our problem – To get Books titles and authors for 4.5 above rating, we can use compose and partial as in the following:
let queryGoodBooks = partial(filter,undefined,filterGoodBooks);let mapTitleAndAuthor = partial(map,undefined,projectTitleAndAuthor)let titleAndAuthorForGoodBooks = compose(mapTitleAndAuthor,queryGoodBooks)
Let’s take some time to understand the position of partial function in the current problem domain. As mentioned, the compose function can only compose a function that takes one argument! However both filter and map take two arguments, so we can't compose them directly.
That’s the reason we have used partial function to partially apply the second argument for both map and filter as you can see here:
partial(filter,undefined,filterGoodBooks);partial(map,undefined,projectTitleAndAuthor)
Here we have passed filterGoodBooks function to query the books that have ratings over 4.5 and passed projectTitleAndAuthor function to take the title and author property from the apressBooks object! Now the returned partial application will expect only one argument, which is nothing but the array itself! Now with these two partial functions in place, we can compose them via compose as we already have done:
Listing 7-4. Using compose function
let titleAndAuthorForGoodBooks = compose(mapTitleAndAuthor,queryGoodBooks)Now the function titleAndAuthorForGoodBooks expects one argument in our case that is apressBooks; let’s pass the object array to it:
titleAndAuthorForGoodBooks(apressBooks)=> [{title: 'C# 6.0',author: 'ANDREW TROELSEN'}]
Wow, we got back exactly what we wanted without compose. But the latest composed version titleAndAuthorForGoodBooks is much more readable and elegant in my opinion. You can sense the importance of creating small units of function that can be again rebuilt using compose as per our needs!
In the same example, what if we want to get only the titles of the books with those above a 4.5 rating? Ah ha, it’s so simple:
let mapTitle = partial(map,undefined,projectTitle)let titleForGoodBooks = compose(mapTitle,queryGoodBooks)//call ittitleForGoodBooks(apressBooks)=> [{title: 'C# 6.0'}]
How about getting only author names for books with ratings that equal 5? That should be easy, right? I leave you to solve this using the above defined functions and the compose function!
Note
In this section, we have used partial to fill the arguments of a function. However you can use curry to do the same thing. It’s just a matter of choice. But can you come up with a solution for using curry in our example above? (Hint: Reverse the order of argument for map, filter).
compose many function
Currently our version of compose function does compose only two given functions. How about composing three, four, or n number of functions? Sadly, our current implementation doesn't handle this. Let’s rewrite our compose function so that it can compose multiple functions on the fly.
Remember that we need to send the output of each function as an input to another function (by remembering the last executed function output recursively). We can use reduce function, which we have used in previous chapters to reduce the n of function calls one at a time. The rewritten compose function now looks like the following:
Listing 7-5. compose many function
const compose = (...fns) =>(value) =>reduce(fns.reverse(),(acc, fn) => fn(acc), value);
Note
The above function is called composeN in source code repo.
The important line of the function is:
reduce(fns.reverse(),(acc, fn) => fn(acc), value);Note
If you recall from the previous chapter, we have used reduce function to reduce the array into a single value (along with an accumulator value; i.e., the third parameter of reduce), for example, to find the sum of the given array, using reduce:
reduce([1,2,3],(acc,it) => it + acc,0)=> 6
Here the array [1,2,3] is reduced into [6]; the accumulator value here is 0.
Here we are first reversing the function array via fns.reverse()and passing the function as (acc, fn) => fn(acc), which is going to call each function one after the other by passing the acc value as its argument. And notably the initial accumulator value is nothing but a value variable, which will be the first input to our function!
With the new compose function in place, let’s go and test it with our old example. In the previous section we composed a function to count words given in a string:
let splitIntoSpaces = (str) => str.split(" ");let count = (array) => array.length;const countWords = compose(count,splitIntoSpaces);//count the wordscountWords("hello your reading about composition")=> 5
Now imagine we want to find out whether the word count in the given string is odd or even. We already have a function for it:
let oddOrEven = (ip) => ip % 2 == 0 ? "even" : "odd"Now with our compose function in place, we can compose these three functions to get what we really want:
const oddOrEvenWords = compose(oddOrEven,count,splitIntoSpaces);oddOrEvenWords("hello your reading about composition")=> ["odd"]
We got back the expected result! Go and play around with our new compose function!
Now we have a solid understanding of how to use compose function to get what we need. In the next section, we are going to see the same concept of compose in a different way called Pipelines.
Pipelines / Sequence
In the previous section, we saw how the compose function data flow works. Yes, the data flow of compose is from left to right, as the functions on the left mostly get executed first, passing on the data to the next function, and so on . . . and the right-most function gets executed at last!
Certain people prefer the other way – where the right-most function gets executed first and the left-most function on the left most gets executed last. As you can remember, the data flow on Unix commands when we do | is from right to left. So in this section, we are going to implement a new function called pipe that does exactly the same thing as the compose function, but just swaps the data flow!
Note
This process of flowing the data from right to left is called pipelines or even sequences! You can call them either PipeLine or Sequences as you prefer.
Implementing pipe
pipe function is just replica of our compose function, the only change is that the data flow:
Listing 7-6. pipe function definition
const pipe = (...fns) =>(value) =>reduce(fns,(acc, fn) => fn(acc), value);
That’s it! Note that there is no more call on fns reverse functions as in compose, which means we are going to execute the function order as it is (from right to left).
Let’s quickly check our implementation of pipe function by rerunning the same example in previous section:
const oddOrEvenWords = pipe(splitIntoSpaces,count,oddOrEven);oddOrEvenWords("hello your reading about composition");=> ["odd"]
The result is going to be the exact same; however, notice that we have changed the order of functions when we do piping! First we call splitIntoSpaces and then count and finally oddOrEven!
Some people (who have the knowledge of shell scripting) prefer pipes over compose. It’s just a personal preference and nothing to do with the underlying implementation. The takeaway is that both pipe and compose do the same thing, but in different data flow! You can use either pipe or compose in your code base, but not both, as it can lead to confusion between your team members! Stick to one style of composing. :)
Odds on Composition
In this section, we discuss two topics. The first discussion is on one of the most important properties of compose - Composition is associative. The second discussion will be on how we debug when we compose many functions!
Let’s tackle one after the other.
Composition is associative
The functional composition is always associative:
compose(f, compose(g, h)) == compose(compose(f, g), h);let’s quickly check our previous section example:
//compose(compose(f, g), h)let oddOrEvenWords = compose(compose(oddOrEven,count),splitIntoSpaces);let oddOrEvenWords("hello your reading about composition")=> ['odd']//compose(f, compose(g, h))let oddOrEvenWords = compose(oddOrEven,compose(count,splitIntoSpaces));let oddOrEvenWords("hello your reading about composition")=> ['odd']
As you can see in the above examples, the result is going to be the same for both the cases! Thus it proves the functional composition is associative. You might be thinking, what is the benefit of compose being associative?
The real benefit is that it allows us to group functions into their own compose! That is:
let countWords = compose(count,splitIntoSpaces)let oddOrEvenWords = compose(oddOrEven,countWords)orlet countOddOrEven = compose(oddOrEven,count)let oddOrEvenWords = compose(countOddOrEven,splitIntoSpaces)or...
The above code is possible just because the composition possesses the associative property! Earlier in the chapter we discussed that creating small functions is the key to compose! Since compose is associative we can create small functions by composition, without any worry, as the result is going to be the same!
Debugging Using tap Function
We have been using compose function quite a lot in this chapter. compose function can compose any number of functions. The data is going to flow from left to right in a chain until the full function list is evaluated! In this section, I'm going to teach you a trick that allows you to debug the failures on compose!
Let’s create a simple function called identity. The aim of this function is to take the argument and return the same argument; hence the name identity:
const identity = (it) => {console.log(it);return it}
Here we have added a simple console.log to print the value this function receives and also return it as it is! Now imagine we have the following call:
compose(oddOrEven,count,splitIntoSpaces)("Test string");When you execute the above code, what if count function throws an error? How will you know what value does count function receive as its argument? And that’s where our little identity function comes into picture. We can add
identity in the flow where we see the error like:
compose(oddOrEven,count,identity,splitIntoSpaces)("Test string");which is going to print the input argument that the count function is going to receive. This little function can be very helpful in debugging what data a function does receive.
Summary
We started this chapter by taking Unix philosophy as an example. We have seen how, by following the Unix philosophy, Unix commands like cat,grep,wc could be able to compose as needed! Then we went ahead and created our own version of the compose function to achieve the same in JavaScript world! The little compose function brings heavy usage to developers as we can compose complex functions as needed from our well-defined small functions. We also saw an example of how currying helps in functional composition, by a partial function.
We also discussed another function called pipe, which does exactly the same thing but inverts the data flow when compared to the compose function. At the end of the chapter we discussed an important property of compose - composition is associative! We also presented a small function called identity that we can use as our debugging tool while facing problems with the compose function!
In the next chapter, we are going to see Functors. Functors are very simple, but at the same time very powerful. We will be seeing the use cases and lot more about Functors in the next chapter! Stay tuned!