Overview: Striving to write pure functions (i.e., functions that are consistent and side-effect free) improves the testability, simplicity, and clarity of code.
What are Pure Functions?
Pure functions are consistent and side-effect free. A consistent function returns the same value every time for a particular set of arguments (this type of function is said to be referentially transparent, as calls to the function can be replaced by the return value without changing the program’s behavior.) A side-effect-free function does not change state through any means beyond its return value, meaning the values that existed before the function call (e.g., global variables, disk contents, static instances, UI, etc.) were not directly altered by the function; and it does not read any state beyond it’s arguments (i.e., no reading of data from files, databases, etc.) Think of pure functions like Mr. Spock: given a set of inputs, you will always get the same straight-forward, logical result (okay, okay, Spock showed an unpredictable, emotional response in “Amok Time”, but c’mon, he thought he had killed Captain Kirk.)
You don’t have to be using some fancy-pants functional programming language to benefit from pure functions. In languages that aren’t purely functional, you’ll have to work to avoid things like side effects and pay attention to whether the arguments you’ve received are copies or references, semantics that are language/context dependent. When dealing with references, you should treat them like you treat dad’s favorite belongings (like a special lamp, for instance): you can look (read the values), but don’t touch (edit the values)!
Examples of Pure and Impure Functions
Let’s work through some example functions and determine if they’re pure (i.e., consistent and side-effect free) or impure.
Below is a trivial example of a JavaScript function that returns the square of a number.
function square(x){
return x * x;
}
Given a particular number x, this square function will always return the same result, so it is consistent. Additionally, it makes no changes to the global state beyond its return value. Therefore, it’s a pure function.
Next, an example Javascript function that checks out a book.
function checkOutBook(book, patron){
if(book.isCheckedOut){
return false;
}
// changes to the book object alter the object beyond the scope of this function
book.isCheckedOut = true;
book.checkedOutTo = patron;
return true;
}
The function is consistent, as passing in a particular set of arguments will always return the same result. However, the function changes some of the properties of the book object, changes that will persist even after the function has returned, so this function has side effects. Therefore, it’s an impure function.
The Benefits of Pure Functions
Pure functions facilitate simplicity and clarity. Because pure functions lack side effects, the outside world is completely abstracted away and the programmer can focus entirely on the parameters and control flow constructs contained within the function. Additionally, when calling a pure function, the programmer can focus solely on the visible context of the call and the return value, as the function has no other impact on state.
Testing pure functions proves extremely straight-forward. All possible paths/states of a pure function can be directly achieved by passing in different sets of arguments. The only things you’ll be mocking are Lions Fans (sure, we didn’t end the season well, but we really could have a great season next… oh, the abject sadness.)
A Usage Strategy for Pure Functions
Because of the benefits of pure functions, I follow the simple rule, “Strive for purity.” That is to say, I work hard to write as many functions as I can in a pure form, and when needed, I write functions that have side effects or are inconsistent.
Side effects aren’t bad. Any meaningful program will have side effects, and it doesn’t bother me in the least when it’s time to write an impure function. However, I try to keep the side effects isolated in small fall-through functions, so as to simplify the simplicity, clarity, and testability of the rest of the code base.
Leave a Reply