Purity in my Programming Please

Pure functions are often hyped up in the Javascript world, probably because of the abundance of state in front end applications. While pure functions have their downsides (i.e. inconvenience, potentially large argument lists), I believe they should be used as much as reasonably possible, and I want to focus on pure functions in Go.

go gopher

What is a Pure Function?

According to Wikipedia, a Pure function has the following properties:

  1. Its return value is the same for the same arguments (no variation with local static variablesnon-local variables, mutable reference arguments or input streams from I/O devices).
  2. Its evaluation has no side effects (no mutation of local static variables, non-local variables, mutable reference arguments or I/O streams).

Which means that as a developer I know two important things:

  1. When I call a pure function I will get the same exact result every time
  2. After calling a pure function my program will be in the same state it was before calling it (other than time will pass and I will have assigned the result of the function)

Because of these properties, pure functions keep applications simple. As we know, simple applications tend to be faster, are easier to test and debug, and are less error prone in general.

Example in Go (golang)

totalCounted := map[string]int{}

func countNamesInText(text string) {
	total := 0
	const name = getNameFromDatabase()
	for _, word := range strings.Split(text, " ") {
		if word == mention {
			total++
		}
	}
	totalCounted[name] = total
}
stop hands
Impure

This function is impure for a couple reasons. Let’s examine each one.

1. Program state is mutated by calling countNamesInText()

Instead of mutating a global variable as a means of “returning” data to the caller, we should return the data via a return statement. We can assume this was done because elsewhere in the program it was a good idea to store of map of counts, but it would be better if that state management was handled outside of our counting function:

func countNamesInText(text string) int {
	totalCounted := 0
	const name = getNameFromDatabase()
	for _, word := range strings.Split(text, " ") {
		if word == mention {
			totalCounted++
		}
	}
	return totalCounted
}

Much better, this function is more “pure” because it will not change the application’s state.

2. Database Argument

Our function is still impure because the “name” value, which affects the result of the function call, is retrieved from a database. In order for our function to be deterministic, that value should instead be passed as a parameter.

Currently, if we wrote the test:

func TestCountNamesInText(t *testing.T) {
	assert.Equal(t, 2, countNamesInText("this word here"))
}

It wouldn’t work consistently. If the database isn’t set up, or if the database was tampered with, our tests will fail. That makes this a bad test, and we wrote the bad test because we have an impure function.

Let’s purify a bit more:

func countNamesInText(text, name string) int {
	totalCounted := 0
	for _, word := range strings.Split(text, " ") {
		if word == mention {
			totalCounted++
		}
	}
	return totalCounted
}

Our function is pure! Now we can have a good test:

func TestCountNamesInText(t *testing.T) {
	assert.Equal(t, 1, countNamesInText("this word here", "this"))
}

And if we include how our application will likely look now including state management and the database call:

totalCounted := map[string]int{}
name := getNameFromDatabase()
totalCounted[name] = countNamesInText("some name in here", name)

More clean code articles for you:
Constants in Go vs Javascript, and When to Use Them
Sorting in Go – Don’t Reinvent This Wheel
How to: Global Constant Maps and Slices in Go

Thanks For Reading

Follow us on Twitter @q_vault if you have any questions or comments

Take game-like coding courses on Qvault Classroom

Subscribe to our Newsletter for more educational articles

%d bloggers like this: