The Simplicity Of Writing Pure Functions
JavaScript, React, Redux
Intro to Pure Functions
Pure functions are highly favored as building blocks in functional programming due to their predictability and potential for being reused.
There are a couple of characteristics that define a function as ‘pure’.
- If given the same input, you will get the same output.
- No side-effects.
Predictable Output
We can pass an argument to a pure function and know that no matter how many times we run that piece of code, we will get the same result.
Check out this example below. No matter what we assign x to, we will get x squared back.
function getSquare(x) {
return x * x;
}
No Side-effects / Independent
Pure functions are always immutable. They should not change any outside variables. Consider the difference between mapping over an array and returning a new array versus using .push() to shovel a new item into an existing array. The ladder will alter the original array and is considered impure. The danger in altering shared states is that another function may have relied on that original state. Now that it has been changed, this can introduce bugs that may be hard to track down.
Purity Test
Let’s put this insight into action.
Which of these examples is pure and which is impure?
Option A:
function double(x) {
return x + x;
}
function doubleAll(list) {
return list.map(double);
} Option B:
function double(x) {
networkCall(x);
return x + x;
}
function doubleAll(list) {
for (let i = 0; i < list.length; i++) {
list[i] = double(list[i]);
}
return list;
}
Answer:
Option A is pure! We can expect to get back a brand new array that has been created by mapping over the original array, but not mutating it in any way.
Option B is impure because it is dependent on the output of a network call and it is looping through a list of items to return an altered original list. If any other functions were dependent on that list, that could cause our program to crash or just not behave the way we intended it to.
React and Redux Usage
When working with React, your app can be made up of a combination of class and functional components. The difference is that functional components consist of pure functions and therefore do not hold any state. They should not mutate any external or shared state. Implementing functional components wherever you can is preferred for optimization as they are the simplest and most reusable of components. It’s also worth noting that these functional components are not meant to contain lifecycle methods.
One of the key principles of Redux is that it requires pure functions to update the global state tree with any incoming changes. We call these pure functions, reducers. They take the previous state and the action that we provide to return the next state, leaving the previous state unchanged.
In the diagram below, you can see how we update the state in Redux without altering the original state. We dispatch an action that is passed to the reducer. The reducer takes the information for the current state and uses a pure function to update the store with a new state based on the action we gave as an argument.
In Closing
According to the Redux documentation, “writing immutable update logic by hand is hard, and accidentally mutating state in reducers is the single most common mistake Redux users make”.
There is a JavaScript library called Immer that takes our mutable code and converts it to an acceptable pure function. There’s even a special method called createSlice that you can use that incorporates Immer automatically. You can read more about it here.
Until then, it’s worthwhile to get comfortable with recognizing and formulating pure functions wherever you can for more optimized, reusable code.