Note
The chapter examples and library source code are in branch chap03. The repo’s URL is: https://github.com/antoaravinth/functional-es6.git
Once checkout the code, please checkout branch chap03:
...
git checkout -b chap03 origin/chap03
...
For running the codes, as before run:
...
npm run playground
...
In the previous chapter we saw how to create simple functions in ES6. We also set up our environment to play around with functional programs using node ecosystem. In fact, we have created our first functional program API called forEach in the previous chapter. There is something special about the forEach function that we have developed in Chapter 2. We passed a function itself as an argument to our forEach function. There is no trick involved there; it’s part of JavaScript specification that a function can be passed as an argument. JavaScript as a language treats functions as data. This is a very powerful concept that allows us to pass functions in place of data. A function that takes another function as its argument is called a Higher-Order function .
We are going to see Higher-Order functions (HOC for short) in this chapter in depth. We will be starting the chapter with a simple example and definition of HOC. Later we will be moving to see more real-world examples of how HOC can help a programmer to solve complex problems easily. As before, we will be adding the HOC functions that we are creating in the chapter in our library. Let’s get started!
Note
We will be creating few higher-order functions and adding it to our library. We are doing this for understanding how things work behind the scenes. The library is good for learning the current resources but they are not production ready for the library. So keep that in mind. :)
Understanding Data
As programmers we know our programs act on data. Data is something that is very important for the consumption of our written program to execute. Hence almost all programming languages give several data for the programmer to work with. For example, we can store the name of a person in String data type. JavaScript offers several data types that we will be seeing in the next subsection. At the end of the section, we will be introduced to a solid definition of higher-order functions, with simple and concise examples.
Understanding JavaScript Data Types
Every programming language has data types. These data types can hold data and allow our program to act upon it. In this little section, we will be seeing JavaScripts’ data types.
In a nutshell, JavaScript as a language supports the following data types:
.Numbers
.Strings
.Booleans
.Objects
.null
.undefined
and importantly, we also have our friend functions as a data type in JavaScript language. Since functions are data types like String, we can pass them around, store them in a variable, etc., very similarly as we do for String and Numbers data types.Functions are First Class Citizens when the language permits them to be used as any other data type, that is, functions can be assigned to variables, passed around as arguments, and can be returned from other functions. In the next section we will see a quick example of what we mean by storing and passing functions around.
Storing a Function
As mentioned in the previous section, functions are nothing but data. Since it’s data, we can hold them in a variable! The below code (Listing 3-1) is literally a valid code in JavaScript context:
Listing 3-1. Storing a Function to Variable
let fn = () => {}In the above code snippet, fn is nothing but a variable that is pointing to a data type function. We can quickly check that fn is of type function by running the following:
typeof fn=> "function"
Since fn is just a reference to our function, we can call it like this:
fn()the above will execute the function that fn points to.
Passing a Function
As day-to-day JavaScript programmers, we know how to pass data to a function. Consider the following function (Listing 3-2), which takes an argument and consoles the type of the argument:
Listing 3-2. tellType Function
var tellType = (arg) => {console.log(typeof arg)}
One can pass the argument to tellType function to see it in action:
let data = 1tellType(data)=> number
Nothing fancy here. As seen in the previous section, we can store even functions in our variable (as functions in JavaScript are data). So how about passing a variable that has reference to a function? Let’s quickly check it:
var dataFn = () => {console.log("I'm a function")}tellType(dataFn)=> function
That’s great! Now we will make our tellType to execute the passed argument as shown in Listing 3-3 if it’s of type function:
Listing 3-3. tellType Executes arg if It’s Function
var tellType = (arg) => {if(typeof arg === "function")arg()elseconsole.log("The passed data is " + arg)}
Here we are checking whether the passed arg is of type function; if so, call it. Remember if a variable is of type function, it means it has a reference to a function that can be executed. That is the reason we are calling arg() if it enters an if statement in the above code snippet.
Let’s execute our tellType function by passing our dataFn variable to it:
tellType(dataFn)=> I'm a function
We have successfully passed a function dataFn to another function tellType, which has executed the passed function. That’s so simple.
Returning a Function
We have seen how to pass a function to another function. Since functions are simple data in JavaScript, we can return them from other functions, too (like other data types).
We’ll take a simple example of a function that returns another function as shown below in Listing 3-4:
Listing 3-4. Crazy Function Return String
let crazy = () => { return String }Note
JavaScript has an in-built function called String. We can use this function to create new string values in JavaScript like this:
String(“HOC”)=> HOC
Note that our crazy function returns a function reference that is pointing to String function. Let’s go and call our crazy function:
crazy()=> String() { [native code] }
As you can see, calling the crazy function returns a String function. Note that it just returns the function reference not executing the function. So we can hold back the returned function reference and call them like this:
let fn = crazy()fn("HOC")=> HOC
or even better like this:
crazy()("HOC")=> HOC
Note
We will be using simple documentation on top of all functions, which are going to return another function. It will be really helpful going forward as it makes reading the source code easy. For example, the crazy function will be documented like this:
//Fn => Stringlet crazy = () => { return String }
Fn => String comment helps the reader understand that crazy function, which executes and returns another function that points to String.
We will be using these sorts of readable comments in our book.
In these sections we have seen functions, which take other functions as its argument and have also seen examples on functions that do not return another function. Now it’s time to bring you to the higher-order function definition:
A Higher-Order Function is a function that receives the function as its argument and/or returns them as outputs.
Abstraction and Higher-Order Functions
Now we have seen how to create and execute higher-order functions. Generally speaking, higher-order functions are written usually to abstract the common problems. In other words, higher-order functions are nothing but defining Abstractions.
In this section we are going to discuss the relationship that higher-order functions has with the term abstraction.
Abstraction Definitions
Wikipedia helps us in getting the definitions of Abstraction:
In software engineering and computer science, abstraction is a technique for managing complexity of computer systems. It works by establishing a level of complexity on which a person interacts with the system, suppressing the more complex details below the current level. The programmer works with an idealized interface (usually well defined) and can add additional levels of functionality that would otherwise be too complex to handle.
and it also includes the following text (which is what we are interested in):
For example, a programmer writing code that involves numerical operations may not be interested in the way numbers are represented in the underlying hardware (e.g. whether they're 16 bit or 32 bit integers), and where those details have been suppressed it can be said that they were abstracted away, leaving simply numbers with which the programmer can work.
The above text clearly gives the idea on abstraction. Abstraction allows us to work on the desired goal but not worrying about the underlying system concepts.
Abstraction via Higher-Order Functions
In this section we will see how higher-order functions help us to achieve the abstraction concept we discussed in the previous section. Here is the code snippet of our forEach function defined in the previous chapter (Listing 2-9):
const forEach = (array,fn) => {for(let i=0;array.length;i++)fn(array[i])}
The above function forEach here has abstracted away the problem of traversing the array. The user of the API forEach doesn't need to understand how forEach have implemented the traversing part, thus abstracting away the problem.
Note
In the forEach function, the passed function fn is called with a single argument as the current iteration content of the array as you can see here:
. . .fn(array[i]). . .
So when the user of the forEach function calls it like this:
forEach([1,2,3],(data) => {//data is passed from forEach function//to this current function as argument})
forEach essentially traverses the array. What about traversing a JavaScript object? Traversing a JavaScript object has steps like this:
Iterate all the keys of the given object.
Identify that the key belongs to its own object.
Get the value of the key if step 2 is true.
Let’s abstract these steps into a higher-order function named forEachObject:
Listing 3-5. forEachObject Function Definition
const forEachObject = (obj,fn) => {for (var property in obj) {if (obj.hasOwnProperty(property)) {//calls the fn with key and value as its argumentfn(property, obj[property])}}}
Note
forEachObject takes the first argument as a JavaScript object (as obj) and the second argument is a function fn. It traverses the object using the above algorithm and calls the fn with key and value as its argument, respectively.
Here they are in action:
let object = {a:1,b:2}forEachObject(object, (k,v) => console.log(k + ":" + v))=> a:1=> b:1
Cool! An important point to note is that both forEach and forEachObject functions are higher-order functions, which allow the developer to work on task (by passing the corresponding function) abstracting away the traversing part! And also since these traversing functions are being abstracted away, we can test them thoroughly, leading to a concise code base. We will be more functional about higher-order functions. Let’s go and implement an abstracted way for handling control flows.
For that, let us create a function called unless. Unless is a simple function, which takes a predicate (that should be either true or false); and if the predicate is false; call the fn as shown below here in Listing 3-6:
Listing 3-6. unless Function Definition
const unless = (predicate,fn) => {if(!predicate)fn()}
With the unless function in place, we can go and write a concise piece of code to find the list of even numbers. The code for it looks like this:
forEach([1,2,3,4,5,6,7],(number) => {unless((number % 2), () => {console.log(number, " is even")})})
The above code when executed is going to print the following:
2 ' is even'4 ' is even'6 ' is even'
In the above case we are getting the even numbers from the array list. What if we want to get the list of even numbers from, say, 0 to 100? We can't use forEach here (of course we can, if we have the array that has [0,1,2.....,100] content). Let’s meet another higher-order function called times. Times is yet another simple higher-order function, which takes the number and calls the passed function as many times as the caller mentioned. The times function looks like what is shown here in Listing 3-7:
Listing 3-7. times Function Definition
const times = (times, fn) => {for (var i = 0; i < times; i++)fn(i);}
Times function looks very similar to the forEach function; it’s just that we are operating on a Number rather than an Array. Now with the times function in place, we can go ahead and solve our problem in hand like this:
times(100, function(n) {unless(n % 2, function() {console.log(n, "is even");});});
That’s going to print our expected answer
0 'is even'2 'is even'4 'is even'6 'is even'8 'is even'10 'is even'. . .. . .94 'is even'96 'is even'98 'is even'
With the above code we have abstracted away looping, and the condition checks into a simple and concise higher-order function!
Having seen a few examples of higher-order functions, it’s time to go into serious mode! In the upcoming section, we will be discussing the real-world higher-order functions and how to create them. So here we go.
Note
All the higher-order functions that we are creating in the current chapter will be in chap03 branch.
Higher-Order Functions in the Real World
In this section we are going to see real-world examples of higher-order functions. We are going to start with simple higher-order functions and slowly grow into complex higher-order functions, which are used by JavaScript developers in their day-to-day lives. Excited? So what are you waiting for? Read on.
Note
The examples will be continued in the next chapters after we introduce the concept of closures. Most of the higher-order Functions work with the help of closures.
every Function
Often as a JavaScript developer we need to check if the array of content is a number, custom object, or anything else. We usually write our typical for loop approach to solve these problems. But let’s abstract these away into a function called every. The every function takes two arguments: an array and a function. It checks if all the elements of the array are evaluated to true by the passed function. The implementation looks like this as shown in Listing 3-8:
Listing 3-8. every Function Definition
const every = (arr,fn) => {let result = true;for(let i=0;i<arr.length;i++)result = result && fn(arr[i])return result}
Here we are simply iterating over the passed array and calling the fn by passing the current content of the array element at the iteration. Note that the passed fn should be returning a Boolean value. Then we do a && to make sure all the contents of the array are obeying the criteria that is given by the fn.
We need to quickly check that our every function works fine. Then pass on the array of NaN and pass fn as isNaN, which does check if the given number is NaN or not:
every([NaN, NaN, NaN], isNaN)=> trueevery([NaN, NaN, 4], isNaN)=> false
Great. The every is a typical higher-order function that is easy to implement and at the same time it’s very useful, too! Before we go further, we need to make ourselves comfortable with the new for..of loop, which is a part of ES6 specifications. The for..of loops can be used to iterate the array elements. Let’s rewrite our ever function with for loop (Listing 3-9:
Listing 3-9. every Function with for-of loop
const every = (arr,fn) => {let result = true;for(const value of arr)result = result && fn(value)return result}
The for..of loop is just an abstraction over our old for loop. As you can see here, the for..of has eliminated the traversing of an array by hiding the index variable, etc. We have abstracted away for..of with every. It’s all about abstraction. What if the next version of JavaScript changes the way of for..of? We just need to change it in the every function. This is one of the biggest advantages of abstraction.
some Function
Similar to every function, we also have a function called some. The some works quite the opposite way of every function such that the some function returns true if either one of the elements in the array returns true for the passed function. The some function is also called as any function. In order to implement some function we use || rather than && :
Listing 3-10. some Function Definition
const some = (arr,fn) => {let result = false;for(const value of arr)result = result || fn(value)return result}
Note
Both every and some functions are inefficient implementations. Every should traverse the array until the first element that doesn’t match the criteria, and some should traverse the array only until the first match. For large arrays they will be inefficient. Remember that we are trying to understand the concepts of higher-order functions in this chapter rather than writing code for efficiency and accuracy.
With some function in place, we can go and check its result by passing the array’s like this:
some([NaN,NaN, 4], isNaN)=>truesome([3,4, 4], isNaN)=>false
Having seen both some and every function, let’s go and look at the sort function and how a higher-order function plays an important role there.
sort Function
The sort is an in-built function that is available in the Array prototype of JavaScript. Suppose we need to sort a list of fruits:
var fruit = ['cherries', 'apples', 'bananas'];you can simply call the sort function that is available on the Array prototype:
fruit.sort()=> ["apples", "bananas", "cherries"]
That’s so simple. The sort function is a higher-order function that takes up a function as its argument, which will help the sort function to decide the sorting logic. Simply put, the signature of the sort function looks like this:
arr.sort([compareFunction])Here the compareFunctionis optional. If the compareFunction is not supplied, elements are sorted by converting them to strings and comparing strings in Unicode code point order. You don’t need to worry about Unicode conversion in this section as we are more focused on the higher-order functions. The important point to note here is that in order to compare the element with our own logic while sorting is performed, we need to pass our compareFunction. We can sense how the sort function is designed to be so flexible in such a way that it can sort any data on the JavaScript world, provided we pass a compareFunction. The sort function is flexible due to the nature of higher-order functions!
Before writing our compareFunction, let’s see what it should really implement. The compareFunction should implement the following logic as mentioned here : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
Listing 3-11. Skeleton of compare Function
function compare(a, b) {if (a is less than b by some ordering criterion) {return -1;}if (a is greater than b by the ordering criterion) {return 1;}// a must be equal to breturn 0;}
As a simple example, imagine we have a list of people:
var people = [{firstname: "aaFirstName", lastname: "cclastName"},{firstname: "ccFirstName", lastname: "aalastName"},{firstname:"bbFirstName", lastname:"bblastName"}];
Now we need to sort people using firstname key in the object, then we need to pass on our own compareFunction like this:
people.sort((a,b) => { return (a.firstname < b.firstname) ? -1 : (a.firstname > b.firstname) ? 1 : 0 })which is going to return the following data:
[ { firstname: 'aaFirstName', lastname: 'cclastName' },{ firstname: 'bbFirstName', lastname: 'bblastName' },{ firstname: 'ccFirstName', lastname: 'aalastName' } ]
Sorting with respect to lastname looks like this:
people.sort((a,b) => { return (a.lastname < b.lastname) ? -1 : (a.lastname > b.lastname) ? 1 : 0 })will return:
[ { firstname: 'ccFirstName', lastname: 'aalastName' },{ firstname: 'bbFirstName', lastname: 'bblastName' },{ firstname: 'aaFirstName', lastname: 'cclastName' } ]
Hooking again into the logic of compareFunction:
function compare(a, b) {if (a is less than b by some ordering criterion) {return -1;}if (a is greater than b by the ordering criterion) {return 1;}// a must be equal to breturn 0;}
Having known the algorithm for our compareFunction, can we do it better? Rather than writing the compareFunction every time, can we abstract away the above logic into a function? As you can see in the above example, we wrote two functions each for comparing firstName and lastName with almost the same duplicate code. Let’s solve this problem with our higher-order function. Now the function that we are going to design won’t take function as its argument but rather return a function. (Remember HOC can also return a function).
Let’s call this function sortBy, which allows the user to sort the array of objects based on the passed property as shown below in Listing 3-12:
Listing 3-12. sortBy function Definition
const sortBy = (property) => {return (a,b) => {var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;return result;}}
The sortBy function takes an argument named property and returns a new function that takes two arguments:
. . .return (a,b) => { }. . .
The returned function has a very simple function body that clearly tells the compareFunction logic:
. . .(a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;. . .
Imagine we are going to call the function with the property name firstname, and then the function body with the replaced property argument looks like the one below:
(a,b) => return (a['firstname'] < b['firstname']) ? -1 : (a['firstname'] > b['firstname']) ? 1 : 0;That’s exactly what we did by manually writing a function. :) Here is our sortBy function in action:
people.sort(sortBy("firstname"))will return:
[ { firstname: 'aaFirstName', lastname: 'cclastName' },{ firstname: 'bbFirstName', lastname: 'bblastName' },{ firstname: 'ccFirstName', lastname: 'aalastName' } ]
Sorting with respect to lastname looks like this:
people.sort(sortBy("lastname"))returns:
[ { firstname: 'ccFirstName', lastname: 'aalastName' },{ firstname: 'bbFirstName', lastname: 'bblastName' },{ firstname: 'aaFirstName', lastname: 'cclastName' } ]
as before!
Wow, that’s truly amazing! The sort function takes the compareFunction, which is returned by the sortBy function! That’s a lot of higher-order functions floating around! Again we have abstracted away the logic behind compareFunction leaving the user to focus on what he or she really needs. After all, a higher-order function is all about abstractions!
But pause for a moment here and think about the sortBy function. Remember that our sortBy function takes a property and returns another function. The returned function is what passed as compareFunction to our sort function. The question here is how come the returned function carries the property argument value that we have passed?
Welcome to the world of closures! The sortBy function works just because JavaScript supports closures. We need to clearly understand what closures are before we go ahead and write higher-order functions. Closures will be the topic of the next chapter.
Remember though, we will be writing our real-world higher-order function after explaining closures in the next chapter!
Summary
We started with simple data types that JavaScript supports. We found that Function is also a data type in JavaScript. Thus, we can keep functions in all the places where we can keep our data. Put in other words, Function can be stored, passed, and reassigned like other data types in JavaScript. This extreme feature of JavaScript allows the Function to be passed over to another function, which we call a Higher-Order Function. Remember that a Higher-Order Function is a function that takes another function as its argument or returns a function. We saw a handful of examples in this chapter showcasing how these Higher-Order Function concepts help the developer to write the code that abstracts away the difficult part! We have created and added a few such functions in our own library! We concluded the chapter by mentioning that Higher-Order Functions work with the blessing of another important concept in JavaScript called Closures. Closures will be the topic of Chapter 4!