Live a pure (function) life

Rowan 🤖
Technical Credit 💸
4 min readDec 2, 2017

During my life as a commercial programmer I have worked on my fair share of mature or legacy projects. Many years of adding new features, bug fixes, and technological shifts often mean that these code bases are in need of a little bit of refactoring.

The thought of refactoring these older and usually large code bases can be daunting for many reasons, including:

  • The original author may have left
  • There are limited or no tests
  • Everything currently works, so refactoring the code is a hard sell to the business
  • The method requiring refactoring has side effects (e.g. updates internal or global state) which increases the risk and scope of the refactoring

The last point increases the risk and scope of refactoring as other code may be dependent on the result of these side effects. These issues can mitigated by the topic of this post: Immutability and Pure Functions.

Immutability

An immutable object is an object whose public state cannot change once it has been created. Any of its public properties must also be immutable e.g. you must use public ImmutableList<TImmutable> {get;} instead of public List<TMutable> {get;}. If you want to modify any of its public properties, then you must create a new copy of it with updated values.

Immutable objects are useful (and thread-safe) because you are guaranteed that the state cannot change. If desirable, you can also easily enforce creation of complete objects only, so no more semi-initialized objects! For example, if you create an instance of an immutable object with a valid initial state, you can pass it as a parameter to as many methods as you like and the object will still be in the exact same state after the methods execute — there is no need to check for null or invalid values on the immutable object after each method call.

The two biggest coding annoyances with using immutable objects are:

  1. Since you can only set the public properties once during initialization, you can end up with constructors that need to take a lot of parameters.
  2. It can feel a bit overkill having to create a new cloned object just to update a single property (if you are on a memory constrained platform, then creating a lot of objects might be an actual issue and not an annoyance)

Personally these annoyances are just a minor price to pay for the peace of mind provided by immutable objects and can be alleviated by using a design pattern such as the builder pattern.

Pure Functions

A pure function is a function (or method) that doesn’t produce any side effects and whose return value is solely derived from its input values and not from any values external to this function. A pure function doesn’t have to do all the heavy lifting itself, it can call other functions as long as they are also pure. Ideally the parameters should all be immutable to ensure that we don’t inadvertently cause them to mutate.

The following basic example demonstrates an impure function being converted into a pure function.

Impure function refactored into a pure function

You can see that the pure version always returns the same output when called multiple times with the same input, whereas the impure version will produce different output when called multiple times with the same input.

Why would we bother refactoring this code to use pure functions? Some of the benefits are:

  • It makes the result predictable. We know that the result will always be the same for the same set of inputs and does not depend on any internal state, so we don’t need to worry about if we are calling it in the correct order or if the object has been correctly initialized.
  • They are easy to test. They only rely on their inputs, so we don’t need to mock network requests or database connections.
  • Expensive pure functions can be cached. Again, since we know that calling a pure function multiple times with the same input will always produce the same output, we can created memoized versions which return cached results.
  • They are thread safe. Since they don’t required any state external to the function’s inputs, we don’t need to lock any objects.
  • Eliminate dead code. Pure functions do not create any side effects, therefore if the result of a pure function call is no longer being used, then the calling code can be safely deleted.

The only drawback I find with writing pure functions is that complex functions sometimes require an unwieldy amount of parameters. In such a case I normally create a class to hold all the parameters e.g. MyPureFunctionParameters and use the good old builder pattern to initialize it.

Wrap-up

Immutability and pure functions both provide the developer with valuable guarantees about the state of their world, and in my experience they produce code that is easier to understand, maintain and test. They are not much more effort to write, and after you get into the habit of writing them, then you might not find yourself writing mutable classes or impure functions again.

Tips for JavaScript programmers

You can use the Immutable.js library if you find that your objects are getting too complex for the spread syntax. If you want to enforce pure functions as well, then you could at writing your apps in Elm and compiling them down to JavaScript.

Tips for C# programmers

For immutable objects you can make use of read-only fields, read-only properties (or expression body definitions), read-only structs, and immutable collections. There is also a Pure attribute but there aren’t any tools to enforce it at the moment.

--

--