-Higher-Order-functions

An in-depth view of Higher-Order functions


Introduction

As complex as it may seem, higher-order functions are functions that either accepts a function as an argument or returns a function or does both.

This is possible because, in JavaScript, functions are first-class citizens — they are treated as variables and can be passed as arguments to other functions and returned by other functions.
Chances are that you have already used a higher-order function on one way or the other: setTimeout and setInterval functions accept functions as parameters.

Creating Higher-Order Functions

Higher-order functions are the bedrock of functional programming and give the opportunity to write pure functions and allow for immutability. However, we won’t be looking at its application in functional programming.

Higher-order functions that return a function


function adjectifier(adjective) {
  return function(noun) {
    return `${adjective} ${noun}`;
  }
}
const coolifier = adjectifier('cool');
console.log(coolifier('article')) // cool article

const beautifier = adjectifier('beautiful');
console.log(beautifier('place')) //beautiful place

Above is a simple example of a higher-order function called adjectifier. It is used to add adjectives to a word. On line 6, coolifier gets assigned the result of calling adjectifier with cool as the argument.
Since adjectifier returns a function, coolifier is now a function which when called appends cool to the argument it is called with.
Line 9 shows another example: beautifier created from adjectifier and returns a string that has beautiful appended to the argument.

One important thing to note from the above example is that both on line 6 and line 9, adjectifier is called with different arguments, and the arguments don’t interfere with the existence of the other. coolifier has no knowledge of beautifier and vice versa. 
Also, the argument, adjective passed to adjectifier is still available after the function is done executing.
This is because there is still a reference to it from the function that gets returned. Hence, it does not get garbage collected.

Let’s look at another example. 


function incrementer(start) {
  return function() {
    return start += 1;
  }
}
const countOne = incrementer(1);
console.log(countOne()); // 2
console.log(countOne()); // 3
console.log(countOne()); // 4

The counter function receives a starting point and returns a function that behaves like a generator function and increases the starting number by 1.
From this example, start can be seen as a private state which cannot be accessed from the outside scope.
This shows one important application of higher-order functions — creating private variables which can be accessed only within the function and by functions which are returned.

Higher-order functions that accept a function as a parameter


function callLater(fn, time) {
  setTimeout(() => {
    if (typeof fn === 'function') {
      fn();
    };
  }, time);
}

function printMe() {
  console.log('James John');
}
callLater(printMe, 1000);
// waits 1000 milli seconds then prints 'James John'

In the above snippet, we created callLater which is a higher-order function. It accepts a function fn as one of its parameters and runs the function after a specified time set in the time argument.
It should be noted that setTimeout is a higher-order function. It receives the anonymous arrow function on line 2 as a parameter and runs it after the time passed as the second argument is elapsed.

Higher-order functions that accept and return functions

Let’s look at an example which will take a function as a parameter and print the result of calling the function, but still maintaining the functionality of the function.
For this, we will create a higher-order function that will behave like a decorator function which will record the time it takes to execute a function, print out that time and still perform the exact functionality expected of the primary function.


/** 
 * A function that returns the full name of the user
 * based on his first and last names.
*/
function getFullName(firstName, lastName) {
  return `${firstName} ${lastName}`;
}

/**
 * Calculate the time taken to run a function.
 * @param {function} fn 
 */
function timedExecution (fn) {
  return function() {
    const start = performance.now();
    const response = fn.call(null, ...arguments);
    const timeSpent = performance.now() - start;
    console.log(`${fn.name} took ${timeSpent} milliseconds to run`)
    return response;
  };
}

const timedGetFullName = timedExecution(getFullName);

const fullName = getFullName('James', 'John');
const timedFullName = timedGetFullName('James', 'John'); 
// getFullName took 0.24500000476837158 milliseconds to run

console.log(fullName); // James John
console.log(timedFullName); // James John

timedExecution here is a higher-order function which accepts a function as an argument and returns a function that does the same thing as the function it received as well as calculating the time taken to execute the function.
On line 15, we record the time before running the function.
Line 16 runs the function using Function.prototype.call, passing all arguments to the function using the ES6 spread ... syntax and captures the return value of the function.
Line 17 calculates the time spent running the function.
Line 18 prints information on the time spent running the function to the console.
Line 19 returns the response of calling the function passed as as argument, thereby making the returned function behave in the same way as the function passed into it. This response was already recorded on line 16.
On line 23, we create a modified version of the getFullName function called timedGetFullName which does the same thing as getFullName with the added functionality of logging the time taken to run the getFullName function.
As you can see, lines 29 and 30 returns the same output when run.

When higher-order functions are used this way — to improve the functionality of a function while maintaining the basic functionality of the function, it is called a decorator.
Creating decorators is one of the major uses of higher-order functions.

Conclusion

Higher-order functions give us enormous functionality: creating private variables, upgrading the functionality of other functions without changing the underlying purpose of the function.
I encourage you to use higher-order functions and make comments on what you found interesting.
The full code for the timedExecution decorator can be found here.


Share on social media

//