Learn pure functions with JavaScript
Pure functions are easier to read, understand, test, debug and munch more. You may have already heard all of that before. A few questions are: what are these pure functions? how do they look like? or can we write the whole application using only pure functions?
Here are two definitions of a pure function:
A pure function is a function that given the same input always returns the same output and has no side effects.
A pure function is a function that has referential transparency.
Reading
Pure functions make code easier to read. Let’s see how.
From the inside
We don’t need to look outside the function scope to understand what is the value of some variable.
There is no need to think how changing the value of the variable in the current function will affect other functions. There is no need to think how changing the value of a variable in other functions will affect the current function.
Look at a pure function:
function isPriorityTodo(task) { return task.type === "RE" && !task.completed; }When reading a pure function you can focus in one place, the current function. Pure functions give the reader less code to read.
From the outside
Look at the code below:
const arr = [1,2,3]; const newValue = compute(arr);console.log(arr); //[1,2,3]When
compute()
is a pure function, we are sure thatconsole
will log[1,2,3]
, we don’t need to read and understand thecompute()
function.Code can be read faster when you read less. Less things to read and understand makes code easier to read.
Side effects
A side effect is a change of a variable from outside the local environment or an interaction with the outside environment that happens during the computation of the result. In short, any interaction with the outside world is a side-effect.
Here are some examples of side-effects:
- reading and changing of state out of local scope
- network calls
- reading and writing to DOM
- listening for user events
- writing to console
- reading and writing to local storage
- using timers (they change the JavaScript Event Loop)
Mutating external state may create unexpected issues in some other distant part of the code. Especially changing input values can create hard to understand bugs. We don’t know where the input object comes from or how changing it will affect other functions.
The result of a function with side effects depends on the external environment. Given the same input it may return different results as the outside state may have changed.
Here are a few functions with side effects:
getNextValue()
is not pure, it changes state outside the function scope.let startValue = 0;function getNextValue(){ startValue += 1; return startValue; }getNextValue(); //1 getNextValue(); //2
addValue()
is not pure, it mutates the input variable.const numbers = []function addValue(list, value){ list.push(value); }addValue(numbers, 1); addValue(numbers, 2);Free variables
A free variable is a variable used in a function that is neither a local variables nor a parameter of the function.
Pure functions don’t access free variable unless this free variables are constants.
A constant is a variable declared with
const
storing an immutable value.Immutability
An immutable value is a value that, once created, cannot be changed.
Pure functions treat input values as immutable. Ideally input values should be immutable values. It is a good practice to return an immutable value.
For more on immutability take a look at Learn immutability with JavaScript.
this
this
represents the function’s context.
this
is an implicit input.When
this
is used, we need to always call the function withcall()
orapply()
and pass in the context object.When
this
is used and we invoke it with the function form, we create a side effect. In this case the function may give different results based on the environment where it is run.this
becomes a free variable.We want explicit inputs on functions. If we want to compose functions easily,
this
should not be used.Making the context object explicit means to add it to the parameter list.
function transform(context) { }I would say to avoid
this
in pure functions.A deterministic function
A pure function is deterministic function. For a specific input it always returns the same output.
Math.random()
orDate.now()
will not be used in pure function as they give different results on each call.The next function has no side-effects, but it is not deterministic. It is not a pure function:
function getNextValue(){ return Math.random(); }Referential transparency
An function has referential transparency if it can be replaced with its output value and the application behavior doesn’t change.
Because we can replace the function with its result it means the function had no side-effects.
A referentially transparent function does nothing else but computing the result.
A function that has referential transparency is a pure function.
Using other functions
Using other pure functions
Pure functions can use other pure functions. Check the code below where the
getBy()
pure function uses other two pure functions:partial()
andqueryContainsBook()
.import partial from "lodash/partial";function queryContainsBook(query, book) { if (query && query.text) { return book.title.includes(query.text); } return true; }function getBy(books, query) { return books.filter(partial(queryContainsBook, query)); }Using other impure functions
Calling an impure function inside a function makes the current function impure. Returning a reference of an impure function doesn’t affect the function purity.
Consider the next example where
doImpureTask()
is an impure function:function doImpureTask(){}function runCallback(){ return doImpureTask(); }function getCallback(){ return doImpureTask; }
runCallback()
is impure.getCallback()
is pure.By taking this idea further, we can create pure function that returns impure functions.
function getCallback(){ return function(){ doImpureTask(); } }
getCallback()
is still a pure function. The returned function is impure.Purity from the inside
We can start writing code in pure functional style by just removing features:
- No use of assignment operators
- Declare variable only with
const
. No use oflet
,var
statements.- Only use array pure methods
- No use of
for
statementsPurity from the outside
Writing the code in a pure functional style will make it pure both from the inside and outside.
Nevertheless, according to the definition, the purity of a function is decided from the outside not from the inside. Look at the next example:
function createActionTypes(config) { const actionTypes = Object.create(null); Object.keys(config).forEach(function addToMap(name) { actionTypes[name] = name; }); return Object.freeze(actionTypes); }The
createActionTypes()
is not written in a pure functional style. It uses the assignment operator and theforEach()
impure method. However, when we look at the function from the outside, it is pure.The problem
The problem with pure functions is that we can’t write the whole application using pure functions. There is state change, there is UI rendering, there is user interaction, there are network calls. All these cannot be implemented with pure functions. Pure functions don’t communicate with the external environment.
What can we do then? We want all the benefits of pure functions, but at the same time we cannot use 100% pure functions to do a practical application.
The benefits of pure functions are significant and we should write as many pure functions as we can. At the same time we need impure code to communicate with the environment.
I suggest to aim for purity and encapsulate impure code.
Impure code
Side effects are a common source of bugs because they make code harder to read and reason about.
Side-effects can make code hard to understand, but they are necessary.
Our best approach is to encapsulate side-effects.
Collecting the impure code together will make it easier to investigate these parts when bugs arise. By isolating side-effects we know where to look for unexpected issues.
We need to reduce the possible kinds of side effects in a function. The goal is to reduce side effects, not just encapsulate them.
Encapsulate impure code
Encapsulation means hiding. The simplest way to encapsulate things in JavaScript is with closures.
Encapsulation with objects
Look at the next example where we limit the places where state can change.
import { List } from "immutable"; import partial from "lodash/partial"; import matchesProperty from "lodash/matchesProperty";function BookStore(){ let books = List(); function add(book) { return books.push(book); } function remove(books, book) { const index = books.findIndex(matchesProperty("id", book.id)); return books.delete(index); }return Object.freeze({ add, remove }); }const bookStore = BookStore(); bookStore.add({ id: 1, title: "How JavaScript Works" });The state in this example is a list of
books
. It can only be modified through theadd()
andremove()
methods of thebookStore
object. State is encapsulated.Encapsulate with a library
A better way is to encapsulate the impure code behind a library. Consider the same store implemented with the
Store()
utility function from a library:import { List } from "immutable"; import Store from "./Store-toolbox"; import partial from "lodash/partial"; import matchesProperty from "lodash/matchesProperty";function add(books, book) { return books.push(book); }function remove(books, book) { const index = books.findIndex(matchesProperty("id", book.id)); return books.delete(index); }export default Store({ state: List(), setters: { add, remove } });Now let’s create the
bookStore
object:import BookStore from "./BookStore";const bookStore = BookStore(); bookStore.add({ id: 2, title: "Functional-Light JavaScript" });This time I wrote only pure functions,
add()
andremove()
, and let the library do the impure part.For the full code take a look at How to create a store using pure functions.
Final Thoughts
Taking all these in consideration, it becomes clear that pure functions make code easier to read and understand. Nevertheless, we should not lose the importance of making pure functions small, do one thing and have an intention revealing names.
We should aim for purity as much as we can and encapsulate side effects. The purpose it to have a big percent of code pure and a low percent impure. This way we end up with a bigger part of the code that is easier to reason about and less code that can cause problems.
The more pure code the better.
For more on the JavaScript’s functional side take a look at:
Learn immutability with JavaScript
Let’s experiment with functional generators and the pipeline operator in JavaScript
Discover Functional Programming in JavaScript with this thorough introduction
How point-free composition will make you a better functional programmer
How to make your code better with intention-revealing function names
Here are a few function decorators you can write from scratch
Learn pure functions with JavaScript – Cristi Salcescu – Medium
Pages: 1 2