© Anto Aravinth, Srikanth Machiraju 2018
Anto Aravinth and Srikanth MachirajuBeginning Functional JavaScripthttps://doi.org/10.1007/978-1-4842-4087-8_7

7. Composition and Pipelines

Anto Aravinth1  and Srikanth Machiraju2
(1)
Chennai, Tamil Nadu, India
(2)
Hyderabad, Andhra Pradesh, India
 

In the previous chapter we saw two important techniques for functional programming: currying and partial application. We discussed how these two techniques work and that as JavaScript programmers we choose 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 to as composition in 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 write our own compose function. Understanding how the compose function can be used to writer cleaner JavaScript is a fun task.

Note

The chapter examples and library source code are in branch chap07. The repo’s URL is https://github.com/antsmartian/functional-es8.git .

Once you check out the code, please check out branch chap07:

...

git checkout -b chap07 origin/chap07

...

For running the codes, as before run:

...

npm run playground

...

Composition in General Terms

Before we see what functional composition is all about, let’s step back and understand the idea behind composition. In this section we explore the idea of composition by using a philosophy that is much more pronounced in the Unix world.

Unix Philosophy

Unix philosophy is a set of ideas that were 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 this book, are supposed to take an argument and return data. Yes, functional programming does follow Unix philosophy.

The second part of the philosophy is this:
  • Expect the output of every program to become the input to another, as yet unknown, program.

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 a 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, and so on, and returns the output (again as similar to a function) to the console. So we can do the following:
cat test.txt
which will print to the console
Hello world

Note

Here the content of 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.txt
which will return the matching content, in this case:
Hello world
We have seen two quite simple functions—grep and cat—that are 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 to do a search. We know that the cat command will return the data; we also 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 world

Note

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 side as an input to a function on the right side! This process, technically, is called s pipeline.

This 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. Here we have created a new function altogether without any effort by combining two existing base functions. Of course, here 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' | wc

Note

The command wc is used to count the words in a given text. This command is available on all Unix and Linux platforms.

This is going to return the data as we expected. As the preceding 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 value.

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 and 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 discuss a use case where functional composition will be useful in the JavaScript world. Stay with us; you’re going to absolutely love the idea of the compose function.

Revisiting map,filter

In Chapter 5, we saw how to chain the data from a map and filter to solve the problem at hand. Let’s quickly revisit the problem and the solution.

We had an array of objects, the structure of which looks like Listing 7-1.
    {
        "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}]
    }
];
Listing 7-1

Apressbook Object Structure, Let apressBooks = [

The problem was to get the title and author objects out of apressBooks for which the review value is greater than 4.5. Our solution to the problem was Listing 7-2.
map(filter(apressBooks, (book) => book.rating[0] > 4.5),(book) => {
    return {title: book.title,author:book.author}
})
Listing 7-2

Getting author Details Using map

For this, the result is the following:
[
        {
                title: 'C# 6.0',
                author: 'ANDREW TROELSEN'
        }
]

The code to achieve the solution tells an important point. The data from our filter function is passed into the map function as its input argument. Yes, you have guessed it correctly: Does it sound like the 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 the compose function.

compose Function

In this section, let’s create our first compose function. Creating a new compose function is easy and straightforward. The compose function needs to take the output of one function and provide it as input to another function. Let’s write a simple compose function in Listing 7-3.
const compose = (a, b) =>
  (c) => a(b(c))
Listing 7-3

compose Function Definition

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 one argument c. When we call the compose 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 function a as input. That’s exactly what a compose function definition is.

Now let’s quickly test our compose function with a simple example before we dive into our running example from the previous section.

Note

The compose function executes b first and passes the return value of b as an argument to the function a. The direction of functions invoked in compose is right to left (i.e., b executes first, followed by a).

Playing with the compose Function

With our compose function in place, let’s build some examples.

Imagine we want to round a given number. The 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 this example, the data (which is the output of the parseFloat function ) is passed as input to Math.round to get a solution; this is the right problem candidate that our compose function will solve.

Let’s solve this via our compose function:
let number = compose(Math.round,parseFloat)
This 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

What we have just done is functional composition! Yes, we have composed two functions to build a new function on the fly! A key point to note here is that the functions Math.round and parseFloat aren’t executed or run until we call our number function .

Now imagine we have two functions:
let splitIntoSpaces = (str) => str.split(" ");
let count = (array) => array.length;
Now if you want to build a new function to count the 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 author simple functions by composing multiple base functions.

curry and partial to the Rescue

We know that we can compose two functions only if this function takes one input argument. That’s not always the case, though, as there can be functions that have multiple arguments. How are we going to compose those functions? Is there something we can do about it?

Yes, we can do it using either the curry or partial functions that we defined in the previous chapter. Earlier in this chapter we used the following code to solve one of the problems (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 take two arguments: The first argument is the array and the second argument is the function to operate on that array. Therefore we cannot compose these two functions directly.

We can, however, take help from partial functions . Remember that the preceding code snippet does work on the apressBooks object. We pull 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 on 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 this:
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 book titles and authors with ratings higher than 4.5—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 the 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 cannot compose them directly.

That’s the reason we have used the 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 the filterGoodBooks function to query the books that have ratings over 4.5 and the projectTitleAndAuthor function to take the title and author properties from the apressBooks object. Now the returned partial application will expect only one argument, which is nothing but the array itself. With these two partial functions in place, we can compose them via compose as we already have done, as shown in Listing 7-4.
let titleAndAuthorForGoodBooks = compose(mapTitleAndAuthor,queryGoodBooks)
Listing 7-4

Using compose Function

Now the function titleAndAuthorForGoodBooks expects one argument, in our case apressBooks; let’s pass the object array to it:
titleAndAuthorForGoodBooks(apressBooks)
=> [
        {
                title: 'C# 6.0',
                author: 'ANDREW TROELSEN'
        }
]

We got back exactly what we wanted without compose, but the latest composed version titleAndAuthorForGoodBooks is much more readable and elegant in our opinion. You can sense the importance of creating small units of function that can be rebuilt using compose as per our needs.

In the same example, what if we want to get only the titles of the books with a rating higher than 4.5? It’s simple:
let mapTitle = partial(map,undefined,projectTitle)
let titleForGoodBooks = compose(mapTitle,queryGoodBooks)
//call it
titleForGoodBooks(apressBooks)
=> [
        {
                title: 'C# 6.0'
        }
]

How about getting only author names for books with ratings that equal 5? That should be easy, right? We leave it to you to solve this using the functions already defined 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. Can you come up with a solution for using curry in our example here? (Hint: Reverse the order of argument for map, filter).

compose Many Functions

Currently our version of the compose function only composes 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 the reduce function, which we used in previous chapters to reduce the n of function calls one at a time. The rewritten compose function now looks like Listing 7-5.
const compose = (...fns) =>
  (value) =>
    reduce(fns.reverse(),(acc, fn) => fn(acc), value);
Listing 7-5

compose many Function

Note

This function is called composeN in the source code repo.

The important line of the function is this:
reduce(fns.reverse(),(acc, fn) => fn(acc), value);

Note

Recall from the previous chapter that we used the 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. 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 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 words
countWords("hello your reading about composition")
=> 5
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 = composeN(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 the 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 and Sequence

In the previous section, we saw that 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, until the rightmost function gets executed last.

Certain people prefer the other way—where the rightmost function gets executed first and the leftmost function gets executed last. As you can remember, the data flow on Unix commands when we do | is from right to left. 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

The pipe function is just a replica of our compose function; the only change is the data flow, as shown in Listing 7-6.
const pipe = (...fns) =>
  (value) =>
    reduce(fns,(acc, fn) => fn(acc), value);
Listing 7-6

pipe Function Definition

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 left to right).

Let’s quickly check our implementation of the pipe function by rerunning the same example as in the 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 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 with different data flow. You can use either pipe or compose in your code base, but not both, as it can lead to confusion among your team members. Stick to one style of composing.

Odds on Composition

In this section, we discuss two topics. The first is one of the most important properties of compose: Composition is associative. The second discussion is on how we debug when we compose many functions.

Let’s tackle one after the other.

Composition Is Associative

Functional composition is always associative. In general, the associative law states the outcome of the expression remains the same irrespective of the order of the parentheses, for example:
x * (y * z) = (x * y) * z = xyz
Likewise,
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 these examples, the result is going to be the same for both cases. Thus it proves the functional composition is associative. You might be wondering what the benefit of compose being associative is?

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)
or
let countOddOrEven = compose(oddOrEven,count)
let oddOrEvenWords = compose(countOddOrEven,splitIntoSpaces)
or
...

This 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 composing. Because compose is associative we can create small functions by composition, without any worry, as the result is going to be the same.

The Pipeline Operator

One other way of composing or chaining base functions is by using the pipeline operator. The pipeline operator is like the Unix pipe operator we saw earlier. The new pipeline operator is intended to make the chained JavaScript functions’ code more readable and extendible.

Note

At the time of writing, the pipeline operator is still at Stage 1 draft (proposal) state in the TC39 approval workflow, which means it is not part of the ECMAScript specification yet. The latest status of this proposal along with browser compatibility will be available at https://github.com/tc39/proposals .

Let us see some examples of the pipeline operator.

Consider the following mathematical functions that operate on a single string argument.
const double = (n) => n * 2;
const increment = (n) => n + 1;
const ntimes = (n) => n * n;
Now, to call these functions on any number, normally we would write the following statement:
ntimes(double(increment(double(double(5)))));
This statement should return a value of 1764. The problem with this statement is the readability, as the sequence of operations or number of the operations is not readable. Linux-like systems use a pipeline operator like the one we saw at the beginning of the chapter. To make the code more readable a similar operator is being added to the ECMAScript 2017 (ECMA8). The name of the operator is pipeline (or binary infix operator), which looks like ‘|>’. The binary infix operator evaluates its left-hand side (LHS) and applies the right-hand side (RHS) to the LHS’s value as a unary function call. Using this operator, the preceding statement can be written as shown here.
5 |> double |> double |> increment |> double |> ntimes  // returns 1764.

That is more readable, isn’t it? Of course, it is easier to read than the nested expressions, contains fewer or no parentheses, and has less indentation. Remember at this point it only works on unary functions, functions with only one argument.

Note

We haven’t had the chance to execute it using the Babel compiler at the time of writing because the operator is in proposal state. You can try the preceding example when the proposal passes Stage 0 (Released) using the latest Babel compiler. You can also use an online Babel compiler like the one at https://babeljs.io/ . The latest status of this proposal’s inclusion into ECMAScript can be watched at http://tc39.github.io/proposal-pipeline-operator/ .

Using the pipeline operator with our earlier example of getting the title and author of highly reviewed books is shown here.
let queryGoodBooks = partial(filter,undefined,filterGoodBooks);
let mapTitleAndAuthor = partial(map,undefined,projectTitleAndAuthor)
let titleAndAuthorForGoodBooks = compose(mapTitleAndAuthor,queryGoodBooks)
titleAndAuthorForGoodBooks(apressBooks)
This can be rewritten more understandably as
apressBooks |> queryGoodBooks |>  mapTitleAndAuthor.

Once again, this operator just a syntactic alternative; the code behind the curtains remains the same, so it is a matter of choice for the developer. However, this pattern saves a few keystrokes by eliminating the effort to name intermediate variables. The GitHub repository for this pipeline operator is https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-pipeline-operator .

Although the pipeline operator works only on unary functions, there is a way around that to use it for functions with multiple arguments. Say we have these functions:
let add = (x, y) => x + y;
let double = (x) => x + x;
// without pipe operator
add(10, double(7))
// with pipe operator
7 |> double |> ( _=> add(10, _ )  // returns 24.

Note

Here the character _ can be replaced with any valid variable name.

Debugging Using the tap Function

We have used the compose function quite a lot in this chapter. The compose function can compose any number of functions. The data are going to flow from left to right in a chain until the full function list is evaluated. In this section, we 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 this code, what if the count function throws an error? How will you know what value the count function receives as its argument? That’s where our little identity function comes into the picture. We can add identity in the flow where we see an error like this:
compose(oddOrEven,count,identity,splitIntoSpaces)("Test string");

That is going to print the input argument that the count function is going to receive. This simple 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, and wc could be able to compose as needed. We created our own version of the compose function to achieve the same in the JavaScript world. The simple compose function is useful 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 introduced the usage of a new pipeline operator (|>) also called the binary infix operator, which can be used with unary functions. The pipeline operator is a proposal to ECMAScript 2017 that is at the proposal stage and will be available soon in the next release of ECMAScript. 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 cover functors. Functors are very simple, but very powerful. We introduce use cases and a lot more about functors in the next chapter.