If you are interested in reading this article in Spanish, check out my blog The Developer's Dungeon
Hey guys, how you been? it has been a while since we take on functional programming right? you might have even thought that I lost the interest and I am learning something new, something cooler, well think again ahah.
I am still in this process and it is a long one I tell you, learning functional programming has been one of the hardest challenges I have taken in quite some time.
Today we are gonna talk about two topics that sometimes get overlooked when learning functional programming, Immutability, and Recursion. So without further ado let's begin.
Immutability
So what do we mean by immutable? let's check the formal definition first:
An immutable object is an object whose state cannot be modified after it is created. This is in contrast to a mutable object (changeable object), which can be modified after it is created
Okay... so an object can not change after it is created. That seems very strange, does it? I mean, we do this all the time, here is an example:
let john = { name:"John", age:18 };
john.name = "Patricio";
So what is exactly wrong about this? well apart from the fact that we now have an object called john
that actually refers to a different person, the rest seems to be okayish, but what happens when you start passing john
around?
const newName = (person) => {
person.name = "Patricio";
};
let john = { name:"John", age:18 };
newName(john);
Now, the user of the newName
function needs to keep in mind that the state of john
has changed, it is no longer the value he originally set it to be, but a function changed it from under his fit without him knowing. This causes very small but hard to find problems when this kind of mutation is happening all over the place in very complex objects. Your code no longer works as expected when you start refactoring and moving things because the logic was dependant on the mutation of the object.
How could we fix this? here is how:
const newName = (person) => {
return {...person, name: "Patricio" };
};
let john = { name:"John", age:18 };
const patricio = newName(john);
Now, when we call the newName
function, a new person object is returned with all the data from john
but we the different name, the state of the original john
object is preserved.
On functional programming languages, this behavior is enforced by default, they require a specific keyword to allow mutation on a data structure or it is impossible to mutate an object altogether. This has the following benefits:
- Don't need to keep a mental track of the state of your objects.
- Don't need to worry about state changing when working on multi-threaded environments, all threads will have the same values.
- Unit testing becomes very easy as all the state that a function needs to be tested is passed through when called and the test only checks the result.
Can we do this in JavaScript? Kind of, there is no runtime feature that prevents us from modifying state all over the place, one thing we can start doing is to use const
as much as we can, this will not apply to deep properties in objects but it is a start. If we wanna go deeper on this road I recommend a third-party library like Immutable JS or Mori, with these libraries we can have lists, records and other types of data structures with the certainty of immutability.
Recursion
Again, let us start by going with the formal definition:
Recursion occurs when a thing is defined in terms of itself or of its type.
Wait, what? 🤯
In basic practical terms, this means that a function will call itself until the process is finished and it is able to exit smoothly. You might be wondering, why in the hell we would want to do that? keeping in mind what we learned about immutability, take a look at this example. We want to print the numbers from 0 to 999 in the console:
for(let i = 0; i < 1000; i++)
{
console.log(i);
}
Did you notice the problem with this? we didn't keep our promise of not mutating state, the variable i
is changing its value with every spin of that loop. Yes, you heard me right, if we are going immutable then for-loops are a dirty business. We can agree that as long as the mutable scope is very small, we should be fine.
But what happens if we work on a purely functional programming language, how we could do that? well here enters recursion.
const sumOneAndLog = (sum) => {
const newSum = sum + 1;
console.log(newSum);
if (newSum < 1000)
sumOneAndLog(newSum);
}
sumOneAndLog(0);
Here we define a function called sumOneAndLog
which is defined in terms of itself, as long as the sum is less than 1000 it will keep calling itself and logging the next number. This is also a very common case on things like game development, where we want to run our game indefinitely until the game ends, we can calculate the state of the game and keep pushing it forward without having a global state.
One last consideration, in JavaScript this kind of behavior is not very well supported. If you would try to do very heavy recursions you would probably blow up the stack very quickly, this is due to the fact that JavaScript engines lack a feature called Tail Call Optimization which allows this to be handled without issues, a workaround is to use a something like a trampoline.
Conclusion
Today we reviewed some key features of functional programming that can make our JavaScript
run much safer and be more readable, what I am aiming with this series is for you to understand that it is not a fight on which paradigm is better, they are different and they behave better in different situations.
I truly believe that a great programmer is the one who is able to write object-oriented, functional, and structured code all the same time(no mention for logic programming, sorry ahah).
If you liked this article, please share and let me know about it below in the comments, if you think there is something I missed please let me know 😄
Top comments (0)