In the previous chapter we saw how to create simple functions in ES8. We also set up our environment to play around with functional programs using a node ecosystem. In fact, we created our first functional program application programming interface (API) called forEach in the previous chapter. There is something special about the forEach function that we 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 the 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 start the chapter with a simple example and definition of HOC. Later we provide 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 to our library. Let’s get started!
We will be creating a few higher order functions and adding them to our library. We are doing this to show how things work behind the scenes. The library is good for learning current resources, but they are not production ready for the library, so keep that in mind.
Note
The chapter examples and library source code are in branch chap03. The repo’s URL is: https://github.com/antsmartian/functional-es8.git
Once you check out the code, please check out branch chap03:
...
git checkout -b chap03 origin/chap03
...
For running the codes, as before run:
...
npm run playground
...
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 the String data type. JavaScript offers several data types that we cover in the next subsection. At the end of the section, we introduce 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 on it. In this brief section, we introduce JavaScript data types.
Numbers
Strings
Booleans
Objects
null
undefined
Importantly, we also have our friend functions as a data type in JavaScript language. Because functions are data types like String, we can pass them around, store them in a variable, and so on. 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 returned from other functions similarly as we do for String and Numbers data. In the next section we provide a quick example of what we mean by storing and passing functions around.
Storing a Function
Storing a Function in Variable
This will execute the function that fn points to.
Passing a Function
tellType Function
tellType Executes arg if It Is a Function
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 code in Listing 3-3.
We have successfully passed a function dataFn to another function tellType , which has executed the passed function. It is that simple.
Returning a Function
We have seen how to pass a function to another function. Because functions are simple data in JavaScript, we can return them from other functions, too (like other data types).
Crazy Function Return String
Note
JavaScript has a built-in function called String. We can use this function to create new string values in JavaScript like this:
String("HOC")
=> HOC
Note
We use simple documentation on top of all functions that 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 => String
let crazy = () => { return String }
The Fn => String comment helps the reader understand that crazy function , which executes and returns another function that points to String.
We use these sorts of readable comments in this book.
In these sections we have seen functions that take other functions as their argument and have also seen examples of functions that do not return another function. Now it’s time to bring you to the definition of a higher order function: a function that receives the function as its argument, returns it as output, or both.
Abstraction and Higher Order Functions
We have seen how to create and execute higher order functions. Generally speaking, higher order functions are usually written to abstract common problems. In other words, higher order functions are nothing but defining abstractions.
In this section we discuss the relationship that higher order functions have with the term abstraction.
Abstraction Definitions
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.
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.
This text clearly gives the idea of abstraction. Abstraction allows us to work on the desired goal without worrying about the underlying system concepts.
Abstraction via Higher Order Functions
The preceding forEach function here has abstracted away the problem of traversing the array. The user of the forEach API does not need to understand how forEach has 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
})
- 1.
Iterate all the keys of the given object.
- 2.
Identify that the key belongs to its own object.
- 3.
Get the value of the key if Step 2 is true.
forEachObject Function Definition
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 precedng algorithm and calls the fn with key and value as its argument, respectively.
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! Because these traversing functions are being abstracted away, we can test them thoroughly, leading to a concise code base. Let’s implement an abstracted way for handling control flows.
unless Function Definition
times Function Definition
With this 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 one step further. In the upcoming section, we will discuss real-world higher order functions and how to create them.
Note
All the higher order functions that we are creating in this chapter will be in the chap03 branch.
Higher Order Functions in the Real World
In this section we will introduce real-world examples of higher order functions. We are going to start with simple higher order functions and slowly move into more 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 are 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
every Function Definition
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 use && to make sure all the contents of the array are obeying the criteria that are given by the fn.
every Function with for..of Loop
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, and so on. 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 most important advantages of abstraction.
some Function
some Function Definition
Note
Both every and some functions are inefficient implementations for large arrays as the every function should traverse the array until the first element that doesn’t match the criteria, and the some function should traverse the array only until the first match. Remember that we are trying to understand the concepts of higher order functions in this chapter rather than writing code for efficiency and accuracy.
Having seen both some and every function, let’s look at the sort function and how a higher order function plays an important role there.
sort Function
Here the compareFunction is 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 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 in the JavaScript world, provided we pass a compareFunction. The sort function is flexible due to the nature of higher order functions!
Skeleton of compare Function
Having known the algorithm for our compareFunction , can we do it better? Rather than writing the compareFunction every time, can we abstract away this logic into a function? As you can see in the preceding 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 a function as its argument but rather return a function. (Remember HOC can also return a function.)
sortBy Function Definition
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.
Pause for a moment here, though, 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 why 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 are the topic of the next chapter.
Remember, though, that 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. 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 developers to write 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, which are the topic of Chapter 4.