Higher-order functions can be intimidating at first, but they’re not that hard to learn. A higher-order function is simply a function that can either accept another function as a parameter or one that returns a function as a result. It’s one of the most useful patterns in JavaScript and has particular importance in functional programming.
We’ll begin with an example that you may be familiar with—JavaScript's handy Array.map()
method. Let's say we have an array of objects that each have a name property like so:
const characters = [
{name: 'Han Solo'},
{name: 'Luke SkyWalker'},
{name: 'Leia Organa'}
];
Now let’s log these names out to the console using the map
method (live code here).
// ES6 syntax
const names = characters.map(character => character.name);
console.log(names); // "Han Solo" "Luke SkyWalker" "Leia Organa"
// ES5 syntax
var names2 = characters.map(function(character) {
return character.name;
});
console.log(names2); // "Han Solo" "Luke SkyWalker" "Leia Organa"
Did you see how the map
method (aka function) took another function as a parameter? That fits our definition of a higher-order function, doesn’t it? Higher-order functions aren’t that scary after all. In fact, you may have been using them all the time without even realizing it.
Now, let’s explore another closely related concept in functional programming. Function composition takes the idea of higher-order functions and allows you to build on it in a very powerful way. We can take small, focused functions and combine them to build complex functionality. By keeping these “building block” functions small, they are easier to test, easier for us to get our heads around and highly reusable.
To see what this looks like, let’s take a look at another example. Don’t worry too much if the code is a bit confusing at first, we’ll step through it in a moment. Let’s begin with the ES6 version:
// ES6 version
const characters = [
{name: 'Luke Skywalker', img: 'http://example.com/img/luke.jpg', species: 'human'},
{name: 'Han Solo', img: 'http://example.com/img/han.jpg', species: 'human'},
{name: 'Leia Organa', img: 'http://example.com/img/leia.jpg', species: 'human'},
{name: 'Chewbacca', img: 'http://example.com/img/chewie.jpg', species: 'wookie'}
];
const humans = data => data.filter(character => character.species === 'human');
const images = data => data.map(character => character.img);
const compose = (func1, func2) => data => func2(func1(data));
const displayCharacterImages = compose(humans, images);
console.log(displayCharacterImages(characters));
/* Logs out the following array
[ "http://example.com/img/luke.jpg",
"http://example.com/img/han.jpg",
"http://example.com/img/leia.jpg"
]
*/
And now the ES5 version of the same code…
// ES5 version
var characters = [
{name: 'Luke Skywalker', img: 'http://example.com/img/luke.jpg', species: 'human'},
{name: 'Han Solo', img: 'http://example.com/img/han.jpg', species: 'human'},
{name: 'Leia Organa', img: 'http://example.com/img/leia.jpg', species: 'human'},
{name: 'Chewbacca', img: 'http://example.com/img/chewie.jpg', species: 'wookie'}
];
var humans = function(data) {
return data.filter(function(character) {
return character.species === 'human';
})
}
var images = function(data) {
return data.map(function(character) {
return character.img;
})
}
function compose(func1, func2) {
return function(data) {
return func2(func1(data));
};
}
var displayCharacterImages = compose(humans, images);
console.log(displayCharacterImages(characters));
/* Logs out the following array
[ "http://example.com/img/luke.jpg",
"http://example.com/img/han.jpg",
"http://example.com/img/leia.jpg"
]
*/
OK, let’s step through this and see if it becomes clear how something like this might be useful. I’ll use the ES5 example during my explanation to keep things as familiar as possible.
1. On the second line we’re declaring an array of objects. It’s very common to receive and work with data in this way—the results of an API call, for example.
2. Then we declare the first function which we call humans
. An important thing to note about this function is that it’s a pure function. That means that given the same input, it will always return the same output. This is a key concept in functional programming. This makes the function easy to test, refactor and, in general, understand.
3. Then we declare the second function images
. Take a look at the callback function passed into map
(which I’ve pulled out for reference below)
function(character) {
return character.img;
}
I’ve used “character” to make the code easier to follow, but if I instead used “item” or something like that, you can see how completely decoupled the function is from the rest of the code. It can be used for any array of objects that have an img
property you want to extract. Yay code reuse!
4. Next we have our higher-order function compose
that takes two functions as parameters and returns another function. This function can also be reused to combine the results of any two functions called from left to right. Pass in the data and let your functions transform it in whatever way required.
5. We then call the compose
function passing in both the humans
and the images
functions and assigning it to the displayCharacterImages
variable. If we were to log the value of displayCharacterImages
right now, we’d see the inner function that is returned by compose
.
function (data) {
return func2(func1(data));
}
6. When we finally call displayCharacterImages
, we pass in the characters
array, which is passed into func1
, which in our case was humans
. That function returns an array of objects that only contains characters that are humans (sorry, Chewie!) and immediately passes it to func2
which is our images
function, which returns an array of images of the human characters in our data set. You can imagine that we might use that array to display the images of those characters on some part of a fan site we have created.
Hopefully, this was a gentle introduction to the topic of higher-order functions and how you might use them in combining small functions together to build complex behavior. If you don’t get it right away, don’t worry. It’s a part of functional programming that can take a bit of getting used to, but once you have a handle on it, can be very powerful and allow you to write flexible code with fewer errors.
Further Reading
If you found this interesting and would like to learn more about functional programming, I have a few good resources for you. The first is Learning Functional Programming with JavaScript, which is a conference talk by Anjana Vakil. There is also a good (and entertaining) video tutorial series from Mattias Petter Johansson (mpj). And finally, for those that prefer an article format, there is Functional Programming In JavaScript — With Practical Examples from Raja Rao and How to Use Map, Filter, & Reduce in JavaScript by Peleke Sengstacke.