lucasamonrc

lucasamonrc

A Very Superficial High-level Overview on Higher-Order Functions in JavaScript

A Very Superficial High-level Overview on Higher-Order Functions in JavaScript

An overview of higher-order functions in JavaScript with practical examples.

Featured on Hashnode

Introduction

What do you think about when you hear/read the term higher-order functions? When I first learned about this term, it meant nothing to me. It sounded like a made-up idea and not something that would help me in my JavaScript development workflow. Turns out that I could not have been more wrong.

You see, truth is that I was using higher-order functions even before I knew what to call them. They are a fundamental feature that makes JavaScript such a fun language to work with.

In this post, we are going to cover the basics of higher-order functions in JavaScript; how and why to use them. We are going to see a few examples and try to write a few of our implementations.

Things that I will expect you to know:

  • Basic programming experience
  • Familiarity with JavaScript syntax
  • let and const variables
  • Arrow functions in JavaScript

With those out of the way, we can start.

Higher-Order Functions

There is much to the term "Higher-Order Function". It echoes back to some pretty interesting math and computer science concepts that are extremely helpful to understand; however, for this tutorial, I will define a higher-order function simply as follows:

A higher-order function is a special type of function that takes another function as an argument or return a new function as its output

Concept

In other words, a higher-order function is an abstraction on top of some procedure that feeds from another function or spits out a new function. By creating this type of abstraction, we start adopting a more declarative approach in programming; telling the computer to do something, instead of specifying to it how to do it.

Consider the example below:

Let's say you have a list of numbers, and you want to output the square of each number. How would you go about doing it? You might try doing something like this:

const nums= [1, 2, 3, 4, 5];

function displaySquares(arr) {
  for (const num of arr) {
    console.log(num * num);
  }
}

displaySquares(nums);

Try running that on your browser's dev tools console tab. You will see it does the task. The example above uses a function to abstract the "display double" task. But inside the function itself, we specify to the computer, imperatively, to iterate through the list and log to the console the square of each element of the list.

Now let's say that in addition to displaying the squares, you also want another function to display the doubles. Well, you can write this function:

function displayDoubles(arr) {
  for (const num of arr) {
    console.log(num * 2);
  }
}

This does the job as well, but nothing changed there. The function is still basically the same. What if we could abstract the operation and declare only one function that cycles through a list and applies any operation on each element? Implementing that creates a higher-order function:

const nums= [1, 2, 3, 4, 5];

function forEach(list, operation) {
  for (const num of list) {
    operation(num);
  }
}

function displaySquare(num) {
  console.log(num * num);
}

function displayDouble(num) {
  console.log(num * 2);
}

forEach(nums, displaySquare);
forEach(nums, displayDouble);

This works, and we also could ditch the declaration of the displaySquare and displayDouble functions and use inline arrow functions instead when calling forEach:

forEach(nums, (num) => console.log(num * num));
forEach(nums, (num) => console.log(num * 2));

Congratulations, you wrote a custom higher-order function.

Application

Now let's be honest. You probably are not going to spend time writing your implementations of this kind of function. However, it is still important to understand, even at a high level, how to implement one.

Having that understanding will help you harness their utility to solve very common problems when developing with JavaScript. These problems may include implementing operations like filter, map, sort, reduce, and search on a data collection in JavaScript.

In the next sections, we will focus on higher-order functions that anyone working with JavaScript must be comfortable using. These are functions from the Array.prototype class; therefore they are methods that must be called by arrays in JavaScript.

Array.prototype.map

"The map() method creates a new array populated with the results of calling a provided function on every element in the calling array." (MDN Web Docs)

This means that we should call the map method when we need to create a new array based on the state of a current array. Let's go back to the example from the previous section. I could accomplish the same result by calling the map method on my number list:

const nums = [1, 2, 3, 4, 5];

const doubles = nums.map((num) => num * 2);
const squares= nums.map((num) => num * num);

console.log(doubles, squares); // [2,4,6,8,10] [1,4,9,16,25]

Notice how much cleaner the code is. Also, notice that function that I am passing to .map receives the num argument, where num represents an element from the array that is calling .map. Therefore the argument function must at obey the following form:

function argument(element) { /* do something */ }

Whatever I return from that function will be a new element that will be included in the array returning from .map.

Check the documentation for other possible arguments.

Array.prototype.filter

"The filter() method creates a new array with all elements that pass the test implemented by the provided function." (MDN Web Docs)

If the provided function returns true, then the element will be included in the returning array, otherwise, it will not.

Let's say I have a list of guests for a party, and I want to write a program that takes that list and returns to me a list with the guests that have confirmed that are coming. This is how I would do that:

const guests = [
  { name: "John", confirmed: false },
  { name: "Susan", confirmed: true},
  { name: "Mary", confirmed: false },
  { name: "Peter", confirmed: true},
  { name: "Pat", confirmed: true},
];

const confirmedGuests = guests.filter((guest) => guest.confirmed);

console.log(confirmedGuests) // [ { name: "Susan", confirmed: true}, { name: "Peter", confirmed: true}, { name: "Pat", confirmed: true} ];

This function works in a very similar way to .map. The difference lies in that the argument function must return true or false in the end.

function argument(element) {
 // do something

 return true; // or false
}

Array.prototype.find

"The find() method returns the value of the first element in the provided array that satisfies the provided testing function. If no values satisfy the testing function, undefined is returned." (MDN Web Docs)

This method is essentially a filter; however, it will return only the first element that passes the predicate (the argument function). So if multiple elements pass the predicate, only the first will be returned. Use it wisely.

const guests = [
  { name: "John", confirmed: false },
  { name: "Susan", confirmed: true},
  { name: "Mary", confirmed: false },
  { name: "Peter", confirmed: true },
  { name: "Peter", confirmed: false },
];

const peter = guests.find((guest) => guest.name === "Peter" );

console.log(peter) // { name: "Peter", confirmed: true }

Array.prototype.reduce

"The reduce() method executes a user-supplied “reducer” callback function on each element of the array, passing in the return value from the calculation on the preceding element. The final result of running the reducer across all elements of the array is a single value." (MDN Web Docs)

Oh boy, this one is a handful. Of all Array higher-order functions in JavaScript, the .reduce method is probably the hardest to grasp at first glance. But once we look at a few examples and explain some of the terminology used in the documentation, it all starts to make sense.

First, let's define the reducer/callback portion. A callback function is a function that is passed down as an argument to another function. Done. This is the accurate term for what we have been calling the "argument function". When we are using the .reduce method, we refer to its callback function as the reducer because this function will be responsible to compute all the elements in the array down to a single value. It must have this signature:

function reducer(accumulator, element) {
  // compute something into a new accumulator
  return newAccumulator;
}

The accumulator parameter refers to the value returned by the previous reducer call. The element parameter refers to the current element of the array calling .reduce.

Let's say I have a list that contains information about the cash flow of my bank account. Each element is a transaction that has a description, type (expense or income), and amount properties. I need to compute my account's total balance from that list, how can I use the .reduce method to accomplish this?


const transactions = [
  { desc: "rent", type: 'exp', amount: 1000 },
  { desc: "paycheck", type: 'inc', amount: 2500 },
  { desc: "book", type: 'exp', amount: 500 },
  { desc: "mcdonalds", type: 'exp', amount: 15 },
  { desc: "groceries", type: 'exp', amount: 120 },
  { desc: "tithing", type: 'exp', amount: 250 },
];

const accountBalance = transactions.reduce((balance, transaction) => {
  let amount = transaction.amount;

  if (transaction.type === 'exp') {
    amount = amount * -1;
  }

  const newBalance = balance + amount;

  return newBalance;
}, 0);

console.log(accountBalance); // 615

Notice how I also added a second optional argument in the .reduce call. That 0 refers to the initial value of the accumulator. I could have omitted that, but it is always a good idea to initialize your accumulator someway, especially if you are planning to reduce your list into an object or some other more complex data structure.

Additional Resources

I hope this was helpful in some way to you. If you would like to learn more about higher-order functions, I strongly recommend checking the resources below:

  • Array - JavaScript | MDN: The official documentation page for the JavaScript array type. From there you can find the documentation page for all available higher-order functions in JavaScript

  • 100 Seconds of Code: This is a very useful playlist that explains useful code concepts in 100 seconds. Pay particular attention to the Array Map in 100 seconds and Array Reduce in 100 seconds videos.

  • Higher-order Function - Wikipedia: Some might argue that you can't trust Wikipedia. I don't know about that. What I do know is that this lengthy entry is really good if you are looking to get more technical on the formal definitions of higher-order functions.

  • Functional-Light JavaScript by Kyle Simpson: Kyle Simpson is a great reference in anything JavaScript, and this book is especially great if you are interested in harnessing the functional features of JavaScript. If you are looking to get a more in-depth perspective of the topics we covered in this article, focus on Chapters 1, 2, and 9.

 
Share this