Higher Order Functions from a Big Brain
Published: 2024-01-09 01:00:45

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]);
  }
}
```

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}!`)
})
```
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;
}
```

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}`;
})
```
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;
}
```

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);
```

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;
}
```

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);
```
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;
}, []);
```
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.