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.
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:
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 return statement
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:
As you can see, we can call it wherever we like, right? For example, we can use our function from another function like this:
Now, let's imagine that we need to add a name argument in our functions:
While this is perfectly okay, imagine that the next day our business requirements changed and they expect another interaction from us, named smile:
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.
And this also prevents us to use functions for more complex scenarios like this:
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).
And to use it, it is simple as this:
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:
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.
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:
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:
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.