Just Enough FP: Pure Functions
Another fundamental of functional programming is a solid understanding of pure functions. A pure function is one in which its output is derived solely from its inputs, with no side effects. I know that might still seem like gibberish to some of you, so I’ll break this down and hopefully make it simple and clear.
The First Part
The first phrase to break down and clarify is “its output is derived solely from its inputs”. Another way to say that is this: given the same arguments, the function always returns the same value. Let’s look at a basic example.
// A pure add function
function add(x, y) {
return x + y
}
// or with an ES6 arrow function
const add = (x, y) => x + y
The add
function will always return the same output given the same arguments. In fact, all functions in math are pure functions, so there’s a good chance you’re already familiar with the concept.
Now, what if the output doesn’t depend on the inputs? Is it still a pure function?
function subtractFromMaxSafeInteger(number) {
return Number.MAX_SAFE_INTEGER - number
}
In the subtractFromMaxSafeInteger
function, given the same input, we always get the same output, right? Sure looks pure.
But it isn’t.
Our output depends on a global constant Number.MAX_SAFE_INTEGER
. This makes it impure, as our output should derive solely from the inputs. If our function was dropped into another program that redefined Number.MAX_SAFE_INTEGER
, we’d end up with different outputs, which would break our function’s purity.
In other words, the “outside world”, that is the scope outside of our function, should have no affect on the output of our function.
We also need to be aware of any impure functions within the function we are evaluating as they will impurify an otherwise pure function. Let me show you an example of what I mean. I’ll create a factory function that makes “friend” objects for a program. I’ll want unique IDs for each “friend”, so I’ll create a generateID
function as well.
// DISCLAIMER: Do not use this to create IDs. There are much better ways. Go find them.
const generateID = () => Math.floor(Math.random() * 10000)
const friendFactory = (name, age) => ({
id: generateID(),
name,
age,
})
I could pass the same name and age to the friend factory, but I’ll get different results each time because generateID
is an impure function.
friendFactory('Anna', 31) // {id: 6777, name: "Anna", age: 31}
friendFactory('Anna', 31) // {id: 6233, name: "Anna", age: 31}
friendFactory('Anna', 31) // {id: 7223, name: "Anna", age: 31}
Notice, that the id
property is different in each object returned from our friendFactory
function. We could make our friendFactory
a pure function again by generating the ID outside of the function’s scope and passing it in as an argument instead.
The Second Part
Now, the second part of my description, “with no side effects”, is a little trickier to understand. The way I like to describe it is that a side effect is a change outside the scope of our function. Let me demonstrate with an example.
let count = 0
const addWithIncrement = (x, y) => {
count++
return x + y
}
addWithIncrement(1, 2)
This addWithIncrement
function will always return the same value given the same inputs, and its output depends solely upon those inputs. However, every time we call this function, we change the value of count
, which is outside the scope of our function.
Another surprising impure function that falls under this category is a function that has an effect on the world outside of our application. For example:
const log = x => {
console.log(x)
}
log('This is an impure function. Wha???')
The log
function doesn’t make a change inside of our program or application, but it affects the outside world, triggering the console to log out the value passed in. This is a side effect, and thus log
is impure.
What Does This Mean?
It means that we need to be aware of which functions in our programs are pure and impure. We need impure functions for our applications to do anything meaningful. Strictly speaking, a program entirely made of pure functions (with no side effects) is equivalent to doing nothing at all.
But, using pure functions throughout our application until we need the side effect to do something meaningful will make our applications easier to test and reason about.
As we will see in future posts, pure functions create the foundation for some very cool functional programming techniques, especially composition.
In my opinion, your goal should be to use more pure functions in your application. Functions that do too much, or have random side effects, can result in buggy applications. Side effect bugs can be some of the most difficult to find and remove.
In this series, I’m going over material from my Just Enough Functional Programming in JavaScript course on egghead. This post is based on the lesson Identify Side Effects by Comparing Pure and Impure Functions.