"Pure function" is one of those terms that might be intimidating at first, but the concept is actually quite simple. In this post, I'll quickly define what pure functions are and why they're good.
Two Criteria
To be considered pure, a function must meet two criteria: it must have (1) identical output for identical inputs and (2) no side effects.
Let's dig deeper into each of these criteria.
Identical output for identical inputs
Let's first consider a function that doesn't have identical output for identical inputs: the greet
function below:
let greeting = 'Hello';
function greet(name) {
return greeting + ' ' + name;
}
console.log(greet('Paul'));
// Hello Paul
We see here that the output of the greet
function can change even if the input name
to that function stays the same (all we have to do is change the greeting
variable value).
How can we make the output identical for identical inputs? It's as easy as just ensuring the greeting
is also an argument for the function:
function greet(greeting, name) {
return greeting + ' ' + name;
}
console.log(greet('Howdy', 'Paul'));
// Howdy Paul
Now, there's nothing we can do to make the greet
function return a different result unless we change one of its input arguments.
No side effects
A side effect is when the function changes something outside the scope of the function. Again, let's take a look at an example of a function that violates the "no side effect" rule:
const user = {
username: "bob1234";
}
let isValid = false;
function validate(user) {
if (user.username.length > 4) {
isValid = true;
}
}
We can see that we're clearly changing the isValid
variable, which is outside the scope of the validate
function. (Note: if your function doesn't return anything, that's a strong indication that it might have side effects!).
So, how do we remove this side effect? Rather than changing an external isValid
variable, we can return whether or not the user is valid from the function itself:
const user = {
username: "bob1234";
}
function validate(user) {
return user.username.length > 4;
}
const isValid = validate(user);
And there you have it--our validate
function no longer changes anything outside its scope.
Why are Pure Functions Good?
You probably have some intuition that these pure function concepts are good, especially if you've experienced first-hand why they're bad. The more you can encapsulate logic, the easier it is to test, and the easier it is to change without worry about what that change will affect.
Let's consider testing our greet
function. In its initial form, we could make the following assertion:
describe('greet', function() {
it('shows a greeting', function() {
expect(greet('Jane')).toEqual('Hello Jane');
});
});
However, if we change our greeting
variable to be "Hi", our test fails! This shouldn't happen; out function is working correctly but it's failing because it depends on a variable outside its scope. When we make the greeting
an argument to the greet
function, this problem goes away as all the information we need to use the function must be supplied as arguments within the test.
Now let's consider our user validation example. How would we even go about testing it? It might be hard to assert a variable external to the function is changed. Furthermore, if we changed the default value of the isValid
variable to true
, the function fails. It definitely doesn't feel right to have this external variable change!
Conclusion
I hope this email provided some insight into what pure functions are and why they're useful!
Top comments (2)
Great post!
Some time ago I wrote a post with this same subject, maybe can be interesting to you.
👉 Pure functions
Memoization might be worth mentioning.