Higher order functions (HOF) are a very powerful concept, in a nutshell we could say that a HOF is a function that takes another function as an argument and/or returns a function, so based on this we could say they are higher order functions because they somehow act as a "parent" or "wrapper" for other functions.
If you are a developer that has worked with functional programming you probably know already what I'm talking about, but still, keep reading!
Example
Let's say we have this requirement:
Implement a function that counts from a given starting point to 100, if the given point is an odd number the function will count in intervals of 5, if in the contrary the number is even then it'll count in intervals of 10. Please, take into consideration that sometimes the user will require to trigger the counter right after providing the starting point but it'll not always be the case, a user might be able to provide a starting point and then require to trigger the counter at a later point in the flow (not immediately after).
so, the first implementation for this without using higher order functions might look like this:
constcounterToOneHundred=startingPoint=>{const isOdd = startingPoint %2;const interval = isOdd ?5:10;for(let i = startingPoint; i <100; i += interval){
console.log(`${i} of 100`);}};
Excellent we got it... right? let's see our checklist:
Receives a starting point
If the starting point is an odd number it counts in intervals of 5
If the starting point is an even number it counts in intervals of 10
It's able to execute the counter immediately after providing the starting point
It's able to execute the counter at a later point in the flow
AH! we're missing one requirement, we almost got it, let's try and check that last element of our list:
const startingPoint =5;// starting point being any numberconstcounterToOneHundred=()=>{const isOdd = startingPoint %2;const interval = isOdd ?5:10;for(let i = startingPoint; i <100; i += interval){
console.log(`${i} of 100`);}};
Now because we took the startingPoint out of the function scope we're able to execute the counter independently from the variable definition, and this means, we can check that last element:
It's able to execute the counter at a later point in the flow
Woohoo! that wasn't so bad, right? but wait there are a couple of things we're missing here:
To be able to define the startingPoint and execute the counter independently we're exposing a variable outside of the implementation of the counter.
We're calculating the intervals when we execute the function but the value required to make this calculation startingPoint is available way before, which means we could have calculated this in advance to avoid doing everything at once inside the function. We could achieve this by moving the definitions of variables isOdd and interval outside of the function but if we do it we'd be exposing more variables outside of the function.
Having exposed variables increases the risk of having mutations in our application, and, therefore inconsistencies.
Ok, that's not good...
I know this now looks like a sad story... but, IT. IS. NOT.
(epic hero entrance).
Higher order functions to the rescue
Fewer words, more code:
constcounterToOneHundred=startingPoint=>{const isOdd = startingPoint %2;const interval = isOdd ?5:10;return()=>{for(let i = startingPoint; i <100; i += interval){
console.log(`${i} of 100`);}};};
BOOM! that's it, have a nice day... just kidding, now let's see our new checklist and then explain the non trivial points:
Super powered checklist:
Receives a starting point: Yes. (Passed as an argument).
If the starting point is an odd number it counts in intervals of 5: Yes.
If the starting point is an even number it counts in intervals of 10: Yes.
It's able to execute the counter immediately after providing the starting point
It's able to execute the counter at a later point in the flow
It keeps the variables encapsulated, isolated from the outer scope.
Makes the calculations for interval when needed.
Point 4. "It's able to execute the counter immediately after providing the starting point"
Yes. When we execute our function like counterToOneHundred(1)() we're defining the variables and returning the anonymous function definition inside in the first function call and then executing the inner function in the second call.
Point 5, "It's able to execute the counter at a later point in the flow" and point 7. "Makes the calculations for interval when needed"
Yes. We can save the return of the first function call and then call the inner function when needed:
The code below saves the definition of the anonymous child function in a variable and makes the interval calculations.
const counter =counterToOneHundred(1);
Then we execute the counter at a later point when needed
counter();
Wonderful!
Point 6, "It keeps the variables encapsulated, isolated from the outer scope"
Since all variables are inside the function scope, that is Affirmative.
So, by making use of a HOF we were able to
Encapsulate our data.
Increase the flexibility of our implementation.
Optimize the code and order of execution of processes.
not too shabby, right?
A more realistic example
Now, it's enough of counters, let's use HOF for a better example, a more realistic one, Imagine we need to create three social share buttons to post our current page on twitter, facebook or Linkedin, these buttons will open a popup when clicking on them depending on the network clicked.
The implementation of this could look something like:
constshare=()=>{/* We setup the data required here to be able to save it in advance */const pageUrl ='https://enmascript.com';const pageTitle ='A place to share about web development and science';const networks ={twitter:`https://twitter.com/share?url=${pageUrl}&text=${pageTitle}`,facebook:`https://www.facebook.com/sharer/sharer.php?u=${pageUrl}`,linkedIn:`https://www.linkedin.com/shareArticle?mini=true&url=${pageUrl}`};/**
* We receive the network type and return a function
* with the event which is binded to the click.
*/returnnetwork=>event=>{
event.preventDefault();/* if the network is not valid return */if(!(network in networks)){returnfalse;}/* open the popup with the selected network */const networkWindow = window.open(
networks[network],'network-popup','height=350,width=600');/* Apply the focus to the popup window after opening it */if(networkWindow.focus){
networkWindow.focus();}};};
And the possible usage of this (let's say on React) would look something like:
/* We setup the data once */const shareOn =share();/* We validate each network and open the popup on click */<div onClick={shareOn('twitter')}><Twitter /></div><div onClick={shareOn('facebook')}><Facebook /></div><div onClick={shareOn('linkedIn')}><LinkedIn /></div>
Cool, right?, on this implementation we're also making use of a concept called Currying, but that is a topic that I'd prefer to tackle in another article.
Great functionalities implemented with Higher Order Functions.
There are many applications for higher order functions, below some functionalities implemented with this approach.
Error Catcher
Allows you to catch javascript errors easily by passing a function definition, it automatically tries to execute it and if it fails then sends a fallback message, you can replace the fallback action with whatever you want.
Implementation
functionerrorCatcher(cb){try{cb();}catch(error){
console.log('Ups, Looks like something went wrong!');}}
Usage
functionsayHi(){const person ={name:'Daniel'};
console.log(`Hi, ${person.name}${person.career.name}`);}errorCatcher(sayHi);
Throttler
Controls the execution of a function throttledFn so that it's executed in intervals of delayTime, especially useful to avoid executing events with an elevated number of sequential executions (scroll events, resize events).
functionloop(){for(i =0; i <1000; i++){
console.log('executing loop to 1000');}}performance(loop);
As you see Higher order functions are very useful, they're widely used and you may have been using them without noticing, they're applied in Object Oriented Programming when using the decorator pattern, they're also used in libraries like express and redux.
I hope you found this article useful, if you did please share with your friends, also you can follow me on Twitter, see you in the next one guys.
Big O notation allows us to evaluate the performance of algorithms so that we can determine their efficiency and make decisions based on this determinations, let's try to understand how this notation works and how we can apply it in our lives as software developers.Read more
Linked Lists are a fundamental data structure in the world of software development, in this article we will explore its implementation and its applications in today's worldRead more