Day 5: Functional programming
From an object-oriented developer's perspective
I really like functional programming. I haven’t yet gotten to use a functional programming language in my professional life (one day, maybe) - but that doesn’t stop me from thinking about my work from a functional perspective. So today I’m writing about functional programming and pure functions, and how we can use these ideas in object-oriented languages.
What is a pure function?
Put simply, a pure function is a function that is deterministic (always returns the same output given the same input), and produces no side effects. The output of a pure function is solely dependent on its input arguments and does not depend on any state or data change during a program's execution.
Pure functions are easy to understand, test, and can be composed and memoized. They are ideal for ensuring clean, maintainable, and testable code.
Pure functions are so awesome, that there is an entire programming paradigm, and family of languages built around the idea, this article’s namesake: functional programming. But we don’t need to write in a functional programming language to enjoy many of the benefits of functional programming, we can apply the ideas to any language we want, as it is in large part a mindset.
Let’s look at an example in an otherwise object-oriented language, here’s a simple class in C# with a method ImpureAdd, adding two numbers together:
public class ImpureAdder
{
private int total = 0;
public int ImpureAdd(int a, int b)
{
total += a + b;
return total;
}
}We’ve got our two criteria for pure functions, so let’s assess our code against them:
Given the same input, does it return the same output? Not necessarily - the behaviour has been encapsulated within an ImpureAdder class which keeps some internal state - the total field. The first time we run ImpureAdd we might expect the correct output (even this is not guaranteed, as other method calls could easily have manipulated total already), but subsequent runs with the same instance will almost certainly yield incorrect results because of the internal state.
Does it product any side effects? Yes it does: it modifies the total field - something not contained within the method itself - which has impact beyond the scope of the method execution.
What might this look like as a pure function?
public int Add(int a, int b)
{
return a + b;
}This method is a pure function because:
Given the same input, it returns the same output - we can see the body of the method only depends on what got passed into it
and
It has no side effects - we are returning the value of a + b, and doing nothing else on the way there
Why should we write code like this?
At the beginning I briefly highlighted some of the benefits of writing pure functions in our code, but I thought I might go in a little bit more depth on some beneficial characteristics that arise when writing this way:
Testability: Code written as a series of pure functions is inherently more testable than code that is not. Tests are easier to setup and execute, as well as assert on, as there are less moving parts we need to concern ourselves with.
Minimises bugs: Many bugs in software arise as a consequence of side effects and objects being in an unexpected state. If we aim to write code using pure functions as much as possible, this is an entire class of error we eliminate. This also goes along with testability - there are fewer test cases we need to write to ensure correctness of the system.
Readability: Code written as a series of pure functions is also inherently more readable than code that is not. The main reason for this is that each function is completely isolated - you have everything you need to know about the function just by looking at its signature and implementation.
Clean: It is hard to write code that is sloppy, while still being deterministic and free of side effects. When we force ourselves to maintain these properties in our code, we tend to end up with shorter methods that do one thing.
These simple examples often feel a little contrive - noone is going to write something like our ImpureAdder class for behaviour so basic as adding two numbers together.
At this point I started to come up with an example that was a little more representative of a real world system - it was about a bank account with deposit and withdraw methods. Obviously, real-world systems eventually need to mutate state - and thus, have side effects - otherwise they’re probably not really doing much. I went down a whole rabbit hole of thread safety, mutex locks, Interlocked.Exchange, and referential transparency - all of which are fascinating and useful topics, but not at 9:13pm when I have other job search stuff I need to be doing.
So I’m going to call it here, and potentially come back to the other example another day.


In recognition of your "real-world systems eventually need to mutate state..." statement, there is an approach to software design that deliberately layers code based on whether it can be purely functional or must have side-effects. It is often called 'functional core, imperative shell' (https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell)