DEV Community

allyn
allyn

Posted on

Higher Order Functions from a Big Brain

Higher order functions(HOFs) are a great way to simplify code when working with arrays. They iterate through the passed array for you and invoke the passed callback function to each iteration of the array. In addition, their return value varies depending on the HOF. You can slice the lines of code you have to write in half, making it more efficient for you.

The main idea with HOFs is that they do a lot of the work for you. The most you have to do is come up with the callback function that will be called on each element of the array. Since you will have an idea of what the contents of the array will be, writing the code becomes simpler because your parameter(s) be used to indicated the type of element(s) you will deal with. And with some of them, you can return a value that is not an array, such as an object, string, or number which can be even less code to write.

Starting off with the .forEach() method.

.forEach()

To help you visualize how .forEach() works, I'll give you an example of how it could be written.

forEach = (array, callback) => {
  for (let i = 0; i < array.length; i++){
    callback(array[i]);
  }
}
Enter fullscreen mode Exit fullscreen mode

This method applies the callback method to each element of the array it is called on and is one of the functions that can return any data type, hence the lack of a return statement in the code.

const arr = ['Allyn', 'Nancy', 'Kelly'];

let forEachExample = arr.forEach((person) => {
  console.log(`Hello ${person}!`)
})
Enter fullscreen mode Exit fullscreen mode

The result of this code does not return an array but instead, logs each element inside the arr array to the console.

Next is the .map() method.

.map()

The .map() method is very similar to .forEach() except it returns a new array that consists of the results from invoking the callback method on each element. Let's take a closer look.

map = (array, callback) => {
  var output = [];
  for (let i = 0; i < array.length; i++){
    output.push(callback(array[i]));
  }
  return output;
}
Enter fullscreen mode Exit fullscreen mode

At a glance, .forEach() and .map() appear nearly identical but the difference is that .map() returns output, an array of the modified elements.

In this example, we'll use an array consisting of objects.

const cities = [
  {
    city: 'New Orleans',
    state: 'Louisiana',
  }, 
  {
    city: 'Chicago',
    state: 'Illinois',
  }, 
  {
    city: 'Los Angeles',
    state: 'California',
  }, 
  {
    city: 'Houston',
    state: 'Texas',
  }
];

let mappedCities = cities.map((place) => {
  return `${place.city}, ${place.state}`;
})
Enter fullscreen mode Exit fullscreen mode

In this example, .map() acts on each element of the cities array and calls the callback function on each element and returns a new array with the modified elements from cities. If you were to log mappedCities, you would see that the cities array now consists of strings in place of the objects and formats the cities in a human-friendly way.

Up next: .filter()

.filter()

Similarly to .map(), .filter() returns an array that contains the return values from the callback function. Except that these values are not modified and they are only returned if their result after invoking the callback function is true.

filter = (array, callback) => {
  var output = [];
  for (let i = 0; i < array.length; i++){
    if (callback(array[i])){
      output.push(array[i]);
    }
  }
  return output;
}
Enter fullscreen mode Exit fullscreen mode

Let's look at an example of how .filter() works.

const students = [
  {
    firstName: 'Sydney',
    grade: 88,
  }, 
  {
    firstName: 'Steve',
    grade: 74,
  }, 
  {
    firstName: 'Matthew',
    grade: 78,
  }, 
  {
    firstName: 'Chris',
    grade: 65,
  }, 
  {
    firstName: 'Patrick',
    grade: 70,
  }, 
  {
    firstName: 'Tyler',
    grade: 99,
  }
];

let over75 = students.filter((student) => student.grade > 75);
Enter fullscreen mode Exit fullscreen mode

When we log over75 to the console, we see that the array is shorter than the students array. That's because over75 only consists of students whose grade was over 75.

On to the more complex HOF: .reduce

.reduce()

What sets .reduce() apart from the rest of these HOFs are the parameters. It takes in a callback function like the rest but there is an additional optional parameter, known as the seed or initial value, that determines what the return value will be and is the starting value of the function. However it can get a little more complicated.

reduce = (array, callback, seed) => {
  let output;
  if (seed === undefined) {
    output = array[0]
    for (let i = 1; i < array.length; i++){
      output = callback(seed, array[1]);
    }
  } else {
    for (let x = 0; x < array.length; x++) {
      output = callback(output, array[i]);
    }
  }
  return output;
}
Enter fullscreen mode Exit fullscreen mode

The callback functions parameters can be referred to as the accumulator and the current value at call time. If an initial value parameter is provided, when the callback function is invoked, the accumulator holds on to the initial value and will determine the return value. If the initial value is not provided, the accumulator will point to the first item of the array. This leads to what the current value parameter will be. The current value parameter will point to the first value of the array if an initial value is not passed, if it is, the current value will point to the next element in the array.

The object of this function is to "reduce" the array into a single value.

Let's look at an example of calling .reduce() on an array without passing a seed.

let nums = [5, 10, 15, 20];

let sum = nums.reduce((acc, curr) => acc + curr);
console.log(sum);
Enter fullscreen mode Exit fullscreen mode

The result of this code is the 50 which is the sum of all the elements of the array, meaning nums has been reduced to 1 number. Without having a seed value, .reduce() started at the first element, 5, which is why the return value is a number.

Now let's look at an example of calling .reduce() but with a seed value this time. For this example, we'll be using the students array from the .map() example.

const students = [
  {
    firstName: 'Sydney',
    grade: 88,
  }, 
  {
    firstName: 'Steve',
    grade: 74,
  }, 
  {
    firstName: 'Matthew',
    grade: 78,
  }, 
  {
    firstName: 'Chris',
    grade: 65,
  }, 
  {
    firstName: 'Patrick',
    grade: 70,
  }, 
  {
    firstName: 'Tyler',
    grade: 99,
  }
];

let studentGrades = students.reduce((acc, curr) => {
  acc.push(curr.grade);
  return acc;
}, []);
Enter fullscreen mode Exit fullscreen mode

When you log studentGrades to the console, you see that instead of there being an array of objects, it's an array of just the grades from all the students.

From what we've seen in these examples, HOFs do a lot of the coding for you, leaving you to attach your HOF of choice to an array and only giving it specific directions as a function to do the work for you. In summation, HOFs can be used for sorting, modifying, filtering, and accumulating and make your code more flexible.

Top comments (2)

Collapse
 
jonrandy profile image
Jon Randy πŸŽ–οΈ • Edited

This article is not really about HOFs at all. It is essentially an article about some useful array methods that just happen to be higher order functions.

Maybe you could consider writing an article explaining what a higher function actually is, possibly with some examples of use cases where writing your own would be appropriate/useful?

Collapse
 
raguay profile image
Richard Guay

In JavaScript, the function can’t use promises if execution order is important. That one caught me really bad once and was very hard to figure out what went wrong.