Remember functions from math class?
For every x, there is a corresponding y.
FP paradigm is based on this idea of treating functions as mathematical functions.
Functions should:
- consistently produces the same output for a given input
- does not cause observable side effects.
Everything that is not a function is a procedure.
Programming is not math. But certainly, we can take advantage of math in programming. There are many benefits of doing so, such as:
- Easier to reason about
- Easier to test
- Easier to debug
The whys are a separate discussion. But for now, let's focus on what the headline of this article promised - the pure functions.
Pure Function
Always has output, for every input.
Check out the code below: What if the input is null or undefined? Or it's anything other than 'sunny,' 'rainy', or 'snowy'?
❌
function getWeatherIcon(weather) {
if (weather === 'sunny') {
return '🌞';
} else if (weather === 'rainy') {
return '🌧';
} else if (weather === 'snowy') {
return '⛄';
}
}
Below, we added a fallback. Now, we have an output for every input.
✅
function getWeatherIcon(weather) {
if (weather === 'sunny') {
return '🌞';
} else if (weather === 'rainy') {
return '🌧';
} else if (weather === 'snowy') {
return '⛄';
} else {
return '🤷'; // fallback
}
}
What about narrowing the range with TypeScript?
function getWeatherIcon(weather: 'sunny' | 'rainy' | 'snowy') {
if (weather === 'sunny') {
return '🌞';
} else if (weather === 'rainy') {
return '🌧';
} else if (weather === 'snowy') {
return '⛄';
}
}
This function is pure - you are not allowed to pass anything other than 'sunny', 'rainy' or 'snowy' ✅
Of course, technically speaking, TypeScript is just a linter, and we are all going to hell for doing FP in JS, but this solution is good enough. This is a contract for developers, making it clear what the function can accept and what it returns.
The output is always the same for the same input.
Pure function - same input, same output:
✅
function add(a, b) {
return a + b;
}
Impure function - same input, different output:
❌
function getRandomEmojii(emojis) {
return emojis[Math.floor(Math.random() * emojis.length)];
}
getRandomEmojii(['🌞', '🌧', '⛄']);
No side effects
What is a side effect? Any change in the system that is observable to the outside world, such as:
- Modifying shared state (such as global variables, variables in parent functions scope)
❌
let post = {
title: 'Pure functions are awesome!',
comments: [],
};
function addComment() {
post.comments.push('Great post!');
}
addComment(post);
addComment(post)
is impure call - it modifies the outside world - global variable post
❌
function addComment(post, comment) {
post.comments.push(comment);
}
addComment(post);
Still impure - comments.push()
modifies the input object
What can we do about it?
✅
function addComment({post, comment}: CommentDTO) {
return {
...post,
comments: [...post.comments, comment],
}
}
Now, we are not modifying the input, we create a new copy of an object with the new copy of comments array. ✅
- Making a network request:
❌
function createComment({title, body, author}: Comment) {
return someAjax('https://api/comments', {
method: 'POST',
body: JSON.stringify({title, body, author})
});
}
As side effects are not avoidable in real-world applications, the way we often deal with that is by isolating pure and impure parts of the code.
For instance, instead of calling someAjax
directly, we could return a function that will eventually make a network request:
✅
function createComment({title, body, author}: Comment) {
return () => someAjax('https://api/comments', {
method: 'POST',
body: JSON.stringify({title, body, author})
});
}
const createCommentRequest = createComment({
title: '...', body: '...', author: '...'
});
// no side effects yet
By doing so, we can handle all side effects in one place, in a controlled way, and keep the rest of the code pure. ✅
Those were some examples of side effects, but there are many more. The point is, pure function should not alter, rely on, or interfere with anything outside of itself, in a way that is observable to the outside.
What it should do is reliably produce the same output for the same input, every time.
TLDR, is this function pure?
- Always has an output for every input. ✅
- The output is always the same for the same input. ✅
- Does not alter/rely on the outside world ✅
Top comments (0)