Using Functions to Abstract Away Complex Logic: Higher Order Functions - Part I

Interconnected Plugs
Photo by Claudio Schwarz | @purzlbaum on Unsplash

I talked about how naming blocks with functions can abstract away general information and ease the mental process. Now, I would like to talk about how functions can create complex yet easy to understand abstractions.

Most languages distinguish a function from a variable, which results in functions being more limited than variables. But, able to use functions in the place of variables is a useful feat. This is known as "first-class citizen function support".

In the early years of computer science; only high-level, functional languages had full first-class function support. But as processors and compilers improved, object-oriented and imperative languages also added support for it under the name of closures/lambdas/anonymous functions. While they are not flexible as their counterpart, they are sufficient to accomplish most of the tasks. So, learning them and using them to abstract information is a good investment.

I'll use JavaScript as I did in my previous post. In JavaScript, there are 2 kinds of functions, functions and arrow functions. While functions can be both named and be anonymous, arrow functions can only be anonymous. Arrow functions are mainly used for their shorter syntax, but actually, there are subtle differences. We won't be talking about those as it is not in the context of this post. So, we can assume functions = anonymous functions for the sake of this post, and apply the concepts to both.

Before starting out, I would like to point out my preference, so it wouldn't interfere with the content. I prefer using named functions in global scope while using anonymous arrow functions for anything else. This helps me to spot general functions easily thanks to the function syntax. Here is the reference:

My Preference

So, the concepts I'll be using are not related to my preference, you can freely use your own style. That out of the way, let's start by talking about a common concept named higher-order functions.

Higher-order functions are the functions that can accept functions as an argument, return functions when executed or both. Here are all of them as snippets:

Functions as argument
Functions as argument

Functions as return statement
Functions as return statement

Both
Both

These might look daunting at first, but they all make sense and easy to understand. If you are unfamiliar with them, I suggest checking those out after reading all the post and try to figure them out.

Let's start basic, imagine having a function like this:
Wave function
As you can see, we can call it wherever we like, right? For example, we can use our function from another function like this:
Calling wave from interact
Now, let's imagine that we need to add a name argument in our functions:
Adding name argument
While this is perfectly okay, imagine that the next day our business requirements changed and they expect another interaction from us, named smile:
Smile function
Now, we have to edit our interact function, but how? As our example is basic, of course, we could use wave and smile without the need to interact function. But that would defeat our purpose here, interact function might handle more things and putting those into wave and smile would complicate them further. Those functions should only be responsible for one thing, waving and smiling.

We could also do something like this with string concatenation, but that also contradicts my previous statement.
String concatenation

And this also prevents us to use functions for more complex scenarios like this:
Edge case
Of course, the answer is Higher-Order Functions! Unlike the fancy name, it is pretty straightforward. We just pass our function as a parameter and call the parameter as if it were a function (it is).
Interaction converted into higher-order function

And to use it, it is simple as this:

Full implementation

This example might seem trivial, but there are countless places HOFs can be used to adhere DRY principle and abstract out common concepts, like loops.

We might have something like this:
Foods and sugars example

We have an array of food objects, each having sugar, protein and water properties. We would like to get just names and sugars from it and save those as another array. To accomplish that, we can utilize a for loop like that. But if we try to add another function like getProteins, it duplicates a lot of code.

getProteins function

If we had a bug in that loop, we would also copy that. And without guessing from the look, understanding what's going on requires some processing in our brain. This is one of the main reasons we have bugs in our code, because most of the time we assume what code does, or precisely, what we expect from it; rather than actually processing.

So, how can we abstract that? We need to look at what is important here, what we can abstract. There should be generic reasoning behind it. As we can see, this function loops over an array and gets each item, processes them and adds those to a new array. This has a name, mapping. So, we could create a HOF like this:
map HOF

What have we done? Simple, we changed 2 things; we pass array we loop as a parameter and we used a parameter named callback, left the processing data part to it. We can use it simply like this:

map HOF with usage

Not we just reduced the lines, we also abstract away mapping. Before, we had to guess or process what it does. But now, we can understand what it does simply by looking up to name, map. It maps, which means it loops the array, processes it and adds them in the same order, with one-by-one correspondence. Now we can use this function anywhere we like. If we had any bug in that, we only have to change that one function. This is so common, it is implemented in JavaScript ten years ago:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

There are other HOFs like this named filter, forEach, flatMap, reduce and the list goes on. I suggest checking them out.

We talked about first-class function support, HOFs, how we can compose a HOF that accepts parameters as functions and a real-life example of that. Next, I'll be talking about composing functions from HOFs, how it can be done and real-life example of that. Then, I'll talk about some useful tricks that can be used with this knowledge. I don't know how many parts will be there, but this is the end of part one. Here is the code I used in this post.

Thanks for joining me in my TED talk :D! Feel free to suggest or criticize. See you in the next part.

Join the conversion now