One of the most useful programming principles is KISS (Keep It Simple, Stupid), but keeping things simple isn’t always easy. So today we’ll look at how to create functions that follow this philosophy.
In JavaScript, functions are a fundamental part of the language. They enable things like currying, functional inheritance, closures, higher-order functions, and more.
“Functions are crucial in computing and mathematics; they help process data in useful ways.”
The Problem with Impurity
A function basically accepts inputs, processes them, and produces an output.
Let’s take the following function as an example:
function sumadorFactory(suma = 0) {
return function (cantidad) {
suma = suma + cantidad;
return suma;
};
}
const sumadorA = sumadorFactory();
sumadorA(2); //2
sumadorA(2); //4
We have a curried function that takes a number (defaults to 0) and returns a function that takes another number and returns the sum.
What’s the problem with this function?
As we can see, we call the same function twice with the same parameter and we get a different result each time. This happens because the function has state and identity.
This makes it more complex than it should be, because to test or compare the function’s result, we need to take time into account.
This also adds complexity when reasoning about it, since the programmer has to keep track of how the code is related and where it gets executed.
“There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult.”
That’s why we create pure functions — to help us stay free from these problems and make testing much simpler.
The Path to Purity
For a function to be pure, it must follow two fundamental rules.
Given the same inputs, it must return the same result, no matter how many times it’s called.
Let’s go back to the previous function and make it pure.
function sumadorFactory(suma = 0) {
return function (cantidad) {
return suma + cantidad;
}
}
const sumadorA = sumadorFactory();
sumadorA(2); //2
sumadorA(2); //2
As you can see, we freed our function from its state. Now every time we call the function with the same parameter, we get the same result.
A function is a special relationship between values: Each input value returns exactly one output value.
The function must not have side effects.
Side effects are everything that happens apart from computing our result — mainly interactions with the external environment or changes to system state.
Let’s look at an example:
//Impuro
var minimo = 10;
function comparaMinimo(num) {
return num < minimo;
}
The comparaMinimo function is impure because it depends on an external state that can change.
A function is a special relationship between values: Each input value returns exactly one output value.
When this communication with the outside world exists, it happens invisibly, making bugs harder to find. That’s why pure functions should only depend on the values passed to them as parameters to compute their result.
Some examples of side effects include: HTTP requests, user input, mutations, printing to the screen, etc.
The Magic of Purity
Pure functions have many advantages, including:
- They’re easier to test, since they’ll always return the same result from the same parameters.
- They can be cached, since they always return the same result, we can store those results in cache (memoize).
- They’re easier to understand: You can replace the function with its result value, making them referentially transparent.
- They can be run in parallel, since they have no external dependencies.
- They reduce the number of bugs, since they don’t alter external state.
This World Won’t Let You Be Pure
Even though all of this sounds great, not all of our code can be pure, because our programs interact with an external environment, so side effects are inherent.
What we need to do is control these side effects, and we’ll cover that in upcoming posts.