DEV Community

Eze Sunday Eze
Eze Sunday Eze

Posted on

Higher-Order Functions in Javascript

Functions are a crucial part of Javascript programming language. In fact, functions are first-class citizens in Javascript.

In programming language design, a first-class citizen in a given programming language is an entity that supports all the operations generally available to other entities. These operations typically include being passed as an argument, returned from a function, and assigned to a variable

In this article, we'll learn the various ways we can leverage the first-class citizenship of Javascript functions and how it allows us to write better software with Higher-Order functions.

First off, let's start with exploring the first-class citizenship features of Javascript.

1). Javascript functions are treated as values that can be assigned to variables as seen in the example below:

```js
let withdraw =  function(amount)=>{
  let fee = 10;
  let processWithdrawal = amount - fee;
  return processWithdrawal;
}
```
Enter fullscreen mode Exit fullscreen mode

In the piece of code above, you’ll notice that the anonymous function is being assigned to the variable withdraw. This is only possible in programming languages that supports first-class functions like Javascript, Haskell, F#, Scala, etc.

2). Also, first-class function languages allows you to pass functions as arguments to other functions like so:

```js
const calculate = function(array, func){
  let output = [];
   for(let index=0; index<array.length; index++){
     output.push(func(array[index]));
   }
   return output;
}
```
Enter fullscreen mode Exit fullscreen mode

In the function above, we passed in an array and a callback function to the anonymous function and inside the anonymous function, we iterate through the array and passed the value of the array to the callback function func to manipulate before pushing to the variable output.

3). Finally, a first-class function allows a function to return another function. Here is an example;

```js 
function speak(animal) {
  return function(sound) {
    return `${animal} ${sound}`;
  };
}

let dog = speak("Dog");
let dogSound = dog("barks");
console.log(dogSound);// Dog barks
```
Enter fullscreen mode Exit fullscreen mode

As seen above, the function speak returns an anonymous function that returns the argument passed to them.

Categorically, a function that accepts functions as arguments or a function that returns a function is referred to as a Higher Order Function in Javascript. We’ve seen examples of such functions in the last two sample code.

There are lots of Higher-Order Functions (HOF) implementation in native Javascript such as Array.prototype.map, Array.prototype.filter, Array.prototype.includes, Array.prototype.reduce. If you’ve noticed, the calculate function example behaves like map except that we didn’t add it to the Array prototype object. If we do add it as shown below, it should look and behave exactly same way.

//
Array.prototype.calculate = function(func) {
  let output = []
  for (let i = 0; i < this.length; i++) {
    output.push(func(this[i]))
  }
  return output;
}
const sum = [1, 2, 3, 4, 5].calculate((item) => {
  return item + 2;
});
const multiply = [1, 2, 3, 4, 5].calculate((item) => {
  return item * 2;
});
const divide = [1, 2, 3, 4, 5].calculate((item) => {
  return item /2 ;
});
Enter fullscreen mode Exit fullscreen mode

But what are the benefits of a higher order function and when should you use them?

When you want your code to be re-useable

Why would you want to write a function in the first place? It’s a no brainer, right? You want to make your code modular, achieve single-responsibility principle (SRP), DRY, and re-useable. Higher-order functions takes it a little further to make it flexible for you to achieve all of these.

For example if we want to achieve the calculate function above without higher order function, we’ll have to write a lot of repeated functions for each arithmetic operation we want to perform. It should look like this:

const add = (array)=>{
  let output = [];
    for (let i = 0; i < array.length; i++) {
       output.push(array[i]+2)
  }
  return output;
}

const multiply = (array)=>{
  let output = [];
    for (let i = 0; i < array.length; i++) {
       output.push(array[i]*2)
  }
  return output;
}

const divide = (array)=>{
  let output = [];
    for (let i = 0; i < array.length; i++) {
       output.push(array[i]/2)
  }
  return output;
}

console.log(add([1, 2, 3, 4, 5]))
console.log(multiply([1, 2, 3, 4, 5]))
console.log(divide([1, 2, 3, 4, 5]))
Enter fullscreen mode Exit fullscreen mode

One thing that is common in the functions above. We are repeating the code and changing only the arithmetic operation thing.

The truth is, you don’t have to write HOF all the time, it doesn’t mean that your code is bad if you don’t use HOF. If your function doesn’t need to be re-used or is pure, there is no point, when you find yourself repeating same code with very little changes to the code you should consider making it a higher-order function.

Behavioural Abstraction

Another very important use case for Higher Order Functions is behavioural abstraction. First order functions allows you to store behaviours that act on data alone. However, high order functions goes further to allow you abstract the functions that act on other functions and consequently act on the data in them.

In our previous example, we showed how a function can return another function. This behaviour can be easily regarded as behavioural abstraction since we can completely abstract the behaviour of the function from the behaviour of the returned function. This concept is also seen in the part where a function accepts another function as an argument as it allows us to influence the behaviour of the more generic function to create our own custom behaviour without modifying the function itself.

function speak(animal) {
  return function(sound) {
    return `${animal} ${sound}`;
  };
}
Enter fullscreen mode Exit fullscreen mode

The anonymous function inside the speak function is a different behaviour, we can delegate some actions we don’t want to implement in the host function speak to the returned function. And when we pass another function, it allows us to compose smaller functions that depend on the more generic function.

Generic Array Manipulations

Another area Higher Order Functions shine is when working with arrays. Often times there are very repetitive tasks that developers do that could easily be created ones and extended based on the need. Some of this data manipulation methods are now supported natively in Javascript. The common ones are filter , map, and reduce.

Assuming you are given an array of dogs and you are required to filter the dogs whose first 3 letters of their name starts with bar and feed them. The end goal here is to feed the dogs. We could easily use the native filter array prototype to select the dogs with these characteristic and implement the feeding mechanism.

Here is a sample code demonstrating this scenario:

let dogs = ["barry", "banga", "barlong", "damilo"];

const dogsToFeed = dogs.filter((dog) =>dog.substr(0, 3) == "bar");

const feedDog = (dogsToFeed) => {
  dogsToFeed.forEach((dog) => {
    console.log("Feeding", dog)
  });
}

feedDog(dogsToFeed)
Enter fullscreen mode Exit fullscreen mode

In this example we also introduced .forEach higher order function as well. It accepts a function as an argument and allows you to manipulate the function elements as you desire without returning anything.

Summation/Combination using Reduce

Reduce is one of the Javascript native higher order function that allows you to compute a single value from an array.

For example, assuming we want to get the total of all the values in an array from 1 to 10 we can easily use a reducer HOF to get an answer quickly.

const total = [1,2,3,4,5,6,7,8,9,10].reduce((accumulator, currentValue)=>accumulator+currentValue);
console.log(total); //55
Enter fullscreen mode Exit fullscreen mode

Hold on, we can even do more. Assuming we have a more complex problem like merging multiple arrays together to form one array. If you have only two arrays, you could easily use the concat method and that will be okay. But with multiple arrays like this [[1,2], [3,4,5], [2,54,6,57,7], [3,4,34,3]] it can be a little tricky. But we could fix that with the reduce HOF.

Here is a sample:

let collections = [[1,2], [3,4,5], [2,54,6,57,7], [3,4,34,3]]
const collection =  collections.reduce((accumulator, currentValue)=>{
  return accumulator.concat(currentValue)
});
console.log(collection) //[1, 2,  3, 4, 5, 2,54, 6, 57, 7, 3, 4,34, 3]
Enter fullscreen mode Exit fullscreen mode

You’ll notice that without changing the reduce function source code we’ve been able to use it to create two different behaviours and solve two different problems.

Summing it all up

The cases where you’ll likely want to use a higher order function are endless. Higher order functions are a very useful tool to help you develop more functional, testable, re-useable, and maintainable code. In the end, you’ll be happy and your team will also be happy when you write better code.

Top comments (0)