The Power of Pure Functions

The Power of Pure Functions

Long Javascript files can quickly feel like the wild west, with variables getting reassigned on the fly with no order whatsoever.

In a desperate attempt to make sense of this all, one of the quickest ways to start cleaning things up is creating pure functions.

Pure functions, by the simplest definition, are functions that take in inputs and return one output value. These functions are "pure" because they don't have side effects in other parts of your code.

You should be able to run a pure function any number of times with the same parameters, and always get the same result

Example

To take an example from Anjana Vakil's excellent talk on functional javascript, let's say we have an array of animals in a zoo, and we want to remove a rat from the zoo.

Non-Pure Function:

const zoo = ['๐Ÿฆ“', '๐Ÿฆ–', '๐Ÿ€'];
// find index of animal to remove
const ratIndex = zoo.findIndex((item) => item.includes('๐Ÿ€'));
// remove item from array
zoo.splice(ratIndex, 1);
console.log(zoo) // => returns ['๐Ÿฆ“', '๐Ÿฆ–']

While this works, there are several problems here.

  • It directly changes the global 'zoo' object
  • If you need to remove a different animal, you have to hard code that in each time
  • It isn't testable in its current condition

So what can we do? Well, we'll just create a pure function that takes in a zoo object, modifies it, and returns the updated function. We can even make it more powerful by passing a second parameter to specify what to remove.

const removeAnimal = (zoo, animal) => {
  const animalIndex = zoo.findIndex(item => item.includes(animal));
  zoo.splice(animalIndex, 1);
  return zoo;
}

Now we have a reusable function that takes in two inputs and returns an updated array. But what advantages does this provide?

Makes Testing Much Easier

Now if you wanted to test this function, you could easily turn the function into its own module and run the tests directly.

import removeAnimal from './removeAnimal';

const zoo = ['๐Ÿฆ“', '๐Ÿฆ–', '๐Ÿ€']
expect(removeAnimal(zoo, '๐Ÿ€')).toEqual(['๐Ÿฆ“', '๐Ÿฆ–']);

Cleans Up Code

Now that you've parsed out this logic into a pure function, you can isolate that code and use it wherever it's needed, increasing readability. Once you do this with several sections of your code, it can start to look like this:

let zoo = ['๐Ÿฆ“', '๐Ÿฆ–', '๐Ÿ€'];
zoo = removeAnimal(zoo, '๐Ÿ€');
zoo = addAnimal(zoo, '๐Ÿฆ–') // => ['๐Ÿฆ“', '๐Ÿฆ–', '๐Ÿฆ–']