Introduction
In Javascript, functions are values ( first-class citizens ). This means that they can be assigned to a variable and/or passed as a value.
let random = function(){
return Math.random()
}
let giveMeRandom = random // assigning random to a variable
This single piece of knowledge allows us to write functional programming in this language. In functional programming, we heavily use higher-order functions.
Higher-order functions?
Higher-order functions are functions that take other functions as arguments or return functions as their results.
Taking an other function as an argument is often referred as a callback function, because it is called back by the higher-order function. This is a concept that Javascript uses a lot.
For example, the map function on arrays is a higher order function. The map function takes a function as an argument.
const double = n => n * 2
[1, 2, 3, 4].map(double) // [ 2, 4, 6, 8 ]
Or, with an anonymous function:
[1, 2, 3, 4].map(function(n){
return n * 2
}) // [ 2, 4, 6, 8 ]
The map function is one of the many higher-order functions built into the language. sort, reduce, filter, forEach are other examples of higher-order functions built into the language.
Higher-order functions allows you to write simpler and more elegant code. Let's look at what the code above would look like without such an abstraction. Let's replace the map function by a loop:
let array = [1, 2, 3, 4]
let newArray = []
for(let i = 0; n < array.length; i++) {
newArray[i] = array[i] * 2
}
newArray // [ 2, 4, 6, 8 ]
The power of composition
One of the great advantages of using higher order functions when we can is composition.
We can create smaller functions that only take care of one piece of logic. Then, we compose more complex functions by using different smaller functions.
This technique reduces bugs and makes our code easier to read and understand.
By learning to use higher-order functions, you can start writing better code.
Example
Lets try with an example. Assume we have a list of grades from a classroom. Our classroom has 5 girls, 5 boys and each of them has a grade between 0 and 20.
var grades = [
{name: 'John', grade: 8, sex: 'M'},
{name: 'Sarah', grade: 12, sex: 'F'},
{name: 'Bob', grade: 16, sex: 'M'},
{name: 'Johnny', grade: 2, sex: 'M'},
{name: 'Ethan', grade: 4, sex: 'M'},
{name: 'Paula', grade: 18, sex: 'F'},
{name: 'Donald', grade: 5, sex: 'M'},
{name: 'Jennifer', grade: 13, sex: 'F'},
{name: 'Courtney', grade: 15, sex: 'F'},
{name: 'Jane', grade: 9, sex: 'F'}
]
I want to know a few things about this:
- The average grade of this classroom
- The average grade of the boys
- The average grade of the girls
- The higher note among the boys
- The higher note among the girls
We will try to use higher-order functions to get a program that is simple and easy to read. Let's start by writing simple functions that can work together:
let isBoy = student => student.sex === 'M'
let isGirl = student => student.sex === 'F'
let getBoys = grades => (
grades.filter(isBoy)
)
let getGirls = grades => (
grades.filter(isGirl)
)
let average = grades => (
grades.reduce((acc, curr) => (
acc + curr.grade
), 0) / grades.length
)
let maxGrade = grades => (
Math.max(...grades.map(student => student.grade))
)
let minGrade = grades => (
Math.min(...grades.map(student => student.grade))
)
I wrote 7 functions, and each of them has one job, and one job only.
isBoy and isGirl are responsible for checking if one student is a boy or a girl.
getBoys and getGirls are responsible for getting all the boys or girls from the classroom.
maxGrade and minGrade are responsible for getting the greatest and lowest grade in some data.
Finally, average is responsible to calculate the average grade of some data.
Notice that the average function doesn't know anything about the type of data it's suppose to process yet. That's the beauty of composition. We can re-use our code in different places. I can just plug this function with others.
Now, we have what we need to write higher-order functions:
let classroomAverage = average(grades) // 10.2
let boysAverage = average(getBoys(grades)) // 7
let girlsAverage = average(getGirls(grades)) // 13.4
let highestGrade = maxGrade(grades) // 18
let lowestGrade = minGrade(grades) // 2
let highestBoysGrade = maxGrade(getBoys(grades)) // 16
let lowestBoysGrade = minGrade(getBoys(grades)) // 2
let highestGirlsGrade = maxGrade(getGirls(grades)) // 18
let lowestGirlsGrade = minGrade(getGirls(grades)) // 9
Notice that the outer functions, average for example, always take as an input the output from the inner functions. Therefore, the only condition to composition is to make sure that the output and input match.
And because each function is responsible for only one thing, it makes our code that much easier to debug and to test.
Top comments (17)
You should also tell about the
closures
when talking about thehigher order functions
. This is the concept most of the javascript developer use but never heard of it.I am working on a separate article about closures ;)
I thought it would be too long of an article if I tried to explain both things in the same post
Wow, Thanks for the nice simple explanation. Turns out I've been using this for years without giving it a name. Also been reading the term HoF for a while without bothering to lookup what it really was.
Excellent post. This helped a lot. I was wondering, what does the zero do here?:
The reduce function takes an optional second parameter that indicates the initial value. If I wanted to start counting from 10,that second parameter would have been 10. Here, I wanted to start counting from 0.
I believe you have set the variable n by mistake inside the for.
let array = [1, 2, 3, 4];
let newArray = [];
for (let i = 0; i < array.length; i++) {
newArray[i] = array[i] * 2;
}
console.log(newArray);
// 2, 4, 6, 8
=============================
High Order Functions
let numbers = [1, 2, 3, 4];
const result = numbers.map((n) => n * 2);
console.log(result);
// 2, 4, 6, 8
I believe you have set the variable n by mistake inside the for.
let array = [1, 2, 3, 4];
let newArray = [];
for (let i = 0; i < array.length; i++) {
newArray[i] = array[i] * 2;
}
console.log(newArray);
// 2, 4, 6, 8
=============================
High Order Functions
let numbers = [1, 2, 3, 4];
const result = numbers.map((n) => n * 2);
console.log(result);
// 2, 4, 6, 8
Sorry, but higher order functions and your example(I mean your example with grades, not first part with map) aren't linked at all. None of the functions from your example takes other functions as arguments or returns function as a result.
Of course, you use functions inside other functions and you pass results of other functions as arguments to other functions, but it is not a higher order function if we look at your definition above.
Good catch.
I believe you have set the variable n by mistake inside the for.
let array = [1, 2, 3, 4];
let newArray = [];
for (let i = 0; i < array.length; i++) {
newArray[i] = array[i] * 2;
}
console.log(newArray);
wow really awesome..this i learn clearly....
1.
Save a variable, time2p2. Assign as its value the result of invoking the timeFuncRuntime() function with the checkThatTwoPlusTwoEqualsFourAMillionTimes() function.
timeFuncRuntime() takes a function as an argument. The argument for timeFuncRuntime() is a callback function: timeFuncRuntime() has to actually call the callback function to see how long it takes to execute. Pass the checkThatTwoPlusTwoEqualsFourAMillionTimes() function as the argument for the timeFuncRuntime() function. Make sure to pass in the function without invoking it... What next to do?
Thank you so much, you really made it simple, easy, and full of great explanation and examples.....