DEV Community 👩‍💻👨‍💻

Cover image for Higher Order Functions - A pragmatic approach
emmanuel ikwuoma
emmanuel ikwuoma

Posted on

Higher Order Functions - A pragmatic approach

Intoducting HoF

Its a common saying that functions are the bread and butter of programming, and the basic unit for building reusable logic in many programming languages, but What makes a function become higher-order?
In simple terms, a HoF is just a kind of function that can accept other function(s) as argument or/and return a function.
Still Not clear? ,Its fine,

a line of code says more than a thousand words. Lets proceed.

There are many different scenarios for approaching HoF but i would list some of the mot common as we continue

Filtering Collections

To demonstrate a simple example, we consider a basic attempt to get only even numbers from a collection, we do the following:

const nums = [1, 2, 3, 6, 8, 11];
const result = [];

for(let i=0; i < nums.length; i++) {
     if(nums[i] % 2 == 0) {
         result.push(i)
     }
     return result;
 }

result     // [2, 6, 8]
Enter fullscreen mode Exit fullscreen mode

This approach seems to work, but if the criteria for selecting the result become a little complicated, things can easily start to look messy, also leaving no room for reusability. A better approach would be to write a custom filtering logic as we do below.

function filter(nums, test) {
      let result = [];
      for(let i=0; i<nums.length; i++) {
          if(test(nums[i])) {
              result.push(nums[i])
          }
      }
      return result;
  }
Enter fullscreen mode Exit fullscreen mode

The function we have just written would expect a collection as its first argument and another function as its second argument, which would be used to perform the selection criteria, now we can easily demonstrate the previous example again.

 let result = filter(nums, num => num % 2 == 0);
 result;      // [2, 6, 8]
Enter fullscreen mode Exit fullscreen mode

It should noted that the custom filter function defined above is only a naive attempt to implement the more robust and efficient inbuilt Array.prototype.filter built-in method, for filtering Array collections.

Grouping

An even more useful application for HoF would be to group collection by say some arbitrary tag, and presenting them in a nicer arrangement.
This is one in many scenarios where higher order function begins to shine. Lets implement the logic to group items

function group(items, groupBy) {
        let grouped = Object.create(null);

        for(let i=0; i < items.length; i++) {
            let tag = groupBy(items[i])
            if(tag in grouped) {
                grouped[tag].push(items[i])
                continue;
            }
            grouped[tag] = [items[i]];

        }

        return grouped;
    }
Enter fullscreen mode Exit fullscreen mode

For this example we would utilize the group function we just defined to re-arrange a collection, using a arbitrary tag.

const items = [
     {tag: "car", name: "tesla", model: "Y"},
     {tag: "smartphone", name: "Samsung", yr: "2019"},
     {tag: "car", name: "mercedes", model: "classic"},
     {tag: "gaming", name: "PS5"},
     {tag: "smartphone", name: "Iphone", yr: "2019"}
]
const tagged = group(items, item => item["tag"]);

tagged   
/*
  {
     car: [
        { tag: 'car', name: 'tesla',model: "Y"},
        { tag: 'car', name: 'mercedes', model: "classic" }
     ],
     smartphone: [
        { tag:'smartphone', name: 'Samsung s9', yr: "2018" },
        { tag:'smartphone', name: 'Iphone 11', yr: "2019" }
     ],
     gaming: [ { tag: 'gaming', name: 'PS5' } ]
    }
*/
Enter fullscreen mode Exit fullscreen mode

Cool right? 😊 With HoF we can easily express this logic and still maintaining readability of our code.

Flattening Arrays

Ill leave you with this attempt to flatten a nested array, of an arbitrary depth. The first attempt would make use of the built-in Array.prototype.reduce. Lets do that.

function flatten(nested) {
    return nested.reduce((flat, next) => {
        return Array.isArray(next) ? [...flat, ...next]
                                   : [...flat, next]
    }, [])
}

const nest = [1, 2, [3, 5], 0]
const deeper = [1, 2, [3, 5, [0, 9, 1]], 0]

flatten(deep)   // [1, 2, 3, 5, 0]
flatten(deeper)   // [1, 2, 3, 5, [0, 9, 1], 0]
Enter fullscreen mode Exit fullscreen mode

Notice that trying to flatten a deeply nested array, seemed not to yield the expected output 😦. However, we can do better, and we try a second approach but this time using the good old recursion technique in combination with Array.prototype.reduce

function flatten(nested) {
    return nested.reduce((flat, next) => {

        if(Array.isArray(next)) {
            return [...flat, ...flatten(next)]
        }

        return [...flat, next]

    }, [])
};

flatten(deeper)  // [1, 2, 3, 5, 0, 9, 1, 0]
Enter fullscreen mode Exit fullscreen mode

Viola, we get the result we expected. It works!!! 😆

Conclusion

In essence higher order functions are not really difficult to understand, although they could look somewhat intimidating at first. Many popular javascript libraries including Redux, use them behind the scenes to expose simple interface for implementing even very complex logic.

I hope you do enjoy this article, as much as i did putting it up. Please leave your review below.

Say hi on twitter 💙
Lovely weekend to you!

Top comments (0)

🌚 Life is too short to browse without dark mode