Note: This is part of the “Javascript and Functional Programming” series on learning functional programming techniques in JavaScript ES6+. Check out the Part 4 , on currying function in JS here.To start from the ground up check out <Part 1>
Damn, this feels good
Motivation
So many of our bugs are rooted in IO related, data mutation, side effect bearing code. These creep up all over our code base — from things like accepting user inputs, receiving an unexpected response via an http call, or writing to the file system. Unfortunately, this is a harsh reality that we should grow accustomed to dealing with. Or is it?
What if I told you, that we could minimize the parts of our code which executed the critical / volatile bits of our program? We could enforce (by convention) that the large majority of our code base would be pure, and limit IO related / side effect bearing code to a specific part of our codebase. This would make our debugging process a lot easier, more coherent and easier to reason about.
So, what is this mythical pure function? A pure function has two main characteristics:
A pure function, in the wild!
const add = (x, y) => x + y // A pure function
add is a pure function because it’s output is solely dependent on the arguments it receives. Therefore, given the same values, it will always produce the same output.
How about this one?
const magicLetter = '*'const createMagicPhrase = (phrase) => `${magicLetter}abra${phrase}`
Something about this one is fishy…. The createMagicPhrase function is dependent on a value which is external to its scope. Therefore, it is not pure!
const fetchLoginToken = externalAPI.getUserToken
Is fetchLoginToken a pure function? Does it return the same value every single time? Absolutely not! Sometimes it will work — sometimes the server will be down and we will get a 500 error — and at some point in the future the API may change so that this call is no longer valid! So, because the function is non-deterministic, we can safely say that it is an impure function.
const calculateBill = (sumOfCart, tax) => sumOfCart * tax
Is calculateBill pure? Definitely :) It exhibits the two necessary characteristics:
The Mostly Adequate Guide states that side effects include, but are not limited to:
Aside from just being awesome
Readability -> Side effects make our code harder to read. Since a non pure function is not deterministic it may return several different values for a given input. We end up writing code that needs to account for the different possibilities. Let’s look at another http based example:
This snippet can fail in so many different ways. What if the id passed to the getTokenFromServer was invalid? What if the server crashed and returned an error, instead of the expected token? There are a lot of contingencies that need to be planned for, and forgetting one (or several!) of them is very easy.
Additionally, a pure function is easier to read, as it requires no context. It receives all of its needed parameters up front, and does not talk / tamper with the state of the application.
Testability -> Because pure functions are deterministic by nature, writing unit tests for them is a lot simpler. Either your function works or it doesn’t 😁
Parallel Code -> Since pure functions only depend on their input, and will not cause side effects, they are great for scenarios where parallel threads run and use shared memory.
Modularity and Reusability -> Think of pure functions as little units of logic. Because they only depend on the input you feed them, you can easily reuse functions between different parts of your codebase or different projects altogether.
Referential Transparency -> This one sounds so complicated 🙄🙄 When I first read the title I wanted a coffee break! Simply put, referential transparency means that a function call could be replaced by its output value, without changing the overall behavior of our program. This is mostly useful as a framework of thought when creating pure functions.
It’s important to note that although pure functions offer a ton of benefits, it’s not realistic to only have pure functions in our applications. After all, if we did our application would have no side effects, thus not produce any observable effects to the outside world. That would be pretty boring 😥😥😥. Instead we will try to encapsulate all of our side effects to specific parts of our codebase. That way, assuming we have written unit tests for our pure functions and know they are working, if something breaks in our app, it will be a lot easier to track down.
Let’s conclude our discussion by converting the following non pure function to pure. This is a contrived example, but demonstrates how we can easily refactor unpure code to pure.
Let’s start by reviewing why this function is unpure. Our function is unpure because it depends on a and b, which are external to its scope. In addition, it is also directly mutating (changing) the values of the variables. The quickest way to refactor this function is
We’ve covered a lot of the benefits of transitioning our code base to include more pure functions. It makes our code easier to reason about, test, and most importantly more predictable. Remember, pure functions are not about completely ridding our code base of side effects. It’s about constraining them to a definitive location and eliminating as much of them as possible. This approach will justify itself many times over, when your programs start growing in size and complexity.
Check out the next post here, where we discuss functional compositions in JS, why they will make your code more readable, and how you can start utilizing them immediately.