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