The Incredible Power of Functions
When learning how to code, one thing I misunderstood was functions. I guess it wasn't a technical misunderstanding, more of a conceptual slash philosophical one, nonetheless, it took me much time to wrap my head around it. Which cost me a bit to write clean code in the functional programming paradigm. Today I would like to share my experience on that.
Worry not, this is not a writing about functional or any kind of paradigm of programming. Nor this is a language-specific one. I will use Typescript for my examples though, but the ideas will be extendable to any language if it supports higher-order functions.
Okay, so what are functions in a programming language? I don't know... let me google...
Functions are blocks of reusable and organized code that usually perform a single, related action...
Which is true. Technically we can write everything in our driver or main function. But it will cause us two problems. We will end up with lots of duplications and our code will be unmaintainable. So a good idea is to create small building blocks of our codes and then reuse and assemble them whenever needed.
That is what I took from my lesson on functions during the early days. But functions have a more robust and interesting purpose. Providing abstraction for a process! I know, a bit much, and we are getting ahead of ourselves.
First, let's ask ourselves a simple question. How small should those building blocks be? Well... those blocks should perform a single action. Fine... okay, case closed right?
Not really, cause what is a single action? We will explore it by solving a problem. This problem is not what we call a "real-life problem", we will discuss a couple of them later. But this problem is one of my favorite ones, especially for understanding abstraction.
This will be an interactive problem though, we will change parts of the statement after we formulate a solution :) Not fair, I know, but we will get to modify our solution too. The only goal of our solution will be to not violate the Open Close Principle. Meaning if we get to a point when we will not need to modify the function we are using to solve this problem, no matter what the change in the statement is, we will succeed.
Enough chit-chat. The problem is simple. Given n, return the sum (cough) of all the numbers (cough) from 1 to n.
Okay, this one is easy though even anyone with just the fundamental knowledge of coding can solve this, right?
Simple, iterating over all the numbers and returning their sum. Now let's give our first modification of the problem, what if we want not just the sum, but the sum of squares of all the numbers? Before we start coding again, let's consider that the next modification might be to calculate the cube of all the numbers or some magical unknown stuff.
Okay let's do this then
Now all we have to do is write a new function for our operation and replace the portion after sum += with our new function and we are done, right? Well, not quite, because we will still need to modify our final function, which violates the Open Close principle.
One idea can be to pass a string to the function and a specific string will identify a specific operation.
Our solution will be okay for a specific set of operations. But will fail for some magical unknown stuff say square root of the number. We will also need to extend our solution function's if else statement to accommodate that.
Here we provide our first abstraction of the process. We will pass our operation as a function parameter to our solution function.
This ability to pass a function as a parameter is the fundamental pillar of functional programming. If a function receives another function as a parameter it is called a higher order function. Fancy naming I know, but understand what it represents. We were able to provide an abstraction to our interface. And doing so we could perform a single operation (not yet but bear with me...) in a single function. And doing so we were able to write clean and beautiful code! Isn't that what life is all about? (cries in loneliness :') )
Now wait, what if someone said I don't want sum, I want product or sum on even numbers, substitution on odd numbers, or something (some magical unknown stuff). Well, the same idea of abstraction goes. We pass another function that will apply and reduce our result.
领英推荐
So instead of += or *= operations in our solution, we just pass it through another abstraction and now our ever-changing problem setter will not have anything else to get us with! But wait, there is a bug in our code. Try to think about it before you go any further. Also, our code is using a loop... There is nothing wrong with it, yet we would like to make it more elegantoooo!!! by using recursion.
The bug was, declaring result = 0. If our reducer is of a product function, it will fail. So my final code is this -
Ha ha, I know :3 Now the code is more declarative and functional... but I think we have gone a bit far down the rabbit hole and it became way too difficult to understand. Personally, I would not recommend coding like this in a collaborative codebase, your teammates might make you a horror incident :3 (don't blame me then).
But all I want you to take away from this is the beauty of it! How we are abstracting away every single process! How we are not violating the single responsibility principle. The mapper function is doing its job and doing one thing only. The same goes for the reducer and map reducer also. And now whatever happens... our anxious problem setter will not throw anything at us that will cause us to modify our solution function... (okay if he says, what if we only want the square sum of the even numbers? But we have come this far, that is also easily doable. Give it a try).
Now let's dive into some "real life problems". How does this idea help us to write clean code?
Our first problem is, let's say we are calling a REST API that returns some paginated results. But we need all the data over all the different pages, so we have to somehow cumulate all of the responses into one single array.
The obvious idea would be to run a while loop and call our backend API.
Honestly, the code looks good, but it has some issues. Our function is tightly coupled with the apiClient. If we want to use a different client for a different call to the backend we will not be able to reuse this function. We can pass apiClient as a parameter, but still not very reusable. For one thing, different clients might need different configurations such as headers. Also, different API endpoints might need different configurations as well, hell we cannot even for sure say that the request will be a get request (it happens). On top of that we are mutating currentUrl and data variable, which is not ideal... but that is another topic for some other day.
So a good design would be to pass the function that calls the API as a parameter.
See? Now if we want to do 10 different things before or after the get API call, our function doesn't need to change at all! And if we need a different apiClient call? We can just pass that function
The last problem(s) we have for today is a React-specific problem. Let's just say we want to design a Welcome banner for our users. Now the banner title will look different on every different webpage to add some razzle dazzle to our application. Say this banner's title will have different colors based on the current weather, and might have different welcome tones. But it will also have some common styles like shadows, alignment, etc.
Now, my first instinct would be to pass the page title as a prop.
Okay, this will render different page titles but what happens to the styling? We can pass it as another prop and apply it to the h1 tag. However, after some days someone said "For the settings page we need a tooltip on the title". You add another boolean prop, isToolTipOn, and another prop for toolTipText. After a couple of days, someone again said, "Oh no... we cannot use h1 tag for this page". You ask "Why tho?", they reply "For shits and giggles, you just develop... developer".
It just doesn't end... So you just drop all those props and send just a renderProp.
And now in all the pages, you do this -
This pattern is called renderProps pattern. React also has another very powerful tool called higher order component or HOC. If you enjoyed reading my scrambled rambling, give it a go.
Well, that's all for today...
Student at National Institute of Technology Rourkela
1 个月Very informative thanks for sharing!
Software Engineer | ASP.NET Core | .NET Core | C# | Optimizely CMS
2 个月Insightful, great writing!
Senior Software Engineer | Python, Golang, Java | Ex Shopee (co-owned by Tencent), ex bKash (co-owned by Alibaba) | 5+ yrs of experience | Residence Permit Holder in Austria: Eligible to work full time in Austria
2 个月Great writing Tamjid Jasir. ??
Algorithm Specialist @ Chaldal | Incoming Noogler | ICPC World Finalist '23
2 个月Enjoyed the way you transformed an imperative ugly code to nice declarative code in the first example ??
Software Technical Lead | React | Node | Java
2 个月wow, nice article