Pure Functional Programming

Functional programming has a long history, with the first programming languages that use functions made in the 1950s. There has been a recent resurgence of the functional programming paradigm, so I think it’s important to be familiar with it.

There are a lot of concepts in functional programming – for example, first class functions, immutable types, lambda calculus, recursion, closure, currying, and monads – so it can be confusing. But at its core the functional paradigm is about one thing:

Pure Functional Programming is simply about avoiding side-effects.

Side-effects

When a program modifies state:

let count = 0 // starting state
const incrementor = () => { count + 1; };

Above is a function that causes a “side-effect” by changing the count variable outside of its scope.

When a program mutates data:

const array = [1,6,4,2];
const sorter = () => { array.sort(); };

The sort method mutates the array, so our [1,6,4,2] state is gone forever. The “side-effect” here is that we’ve broken any other program that relied on the order in array.

When a program depends on anything other than its arguments:

The examples above have an additional problem. The functions depend on count and array which are not passed in as arguments.

When a program interacts with I/O:

const myLogger = (output) => { console.log("Log:", output); };

A program that raises an error, writes data to disk, or prints something on the screen is causing a “side-effect.”

Problems with side-effects

A program that has side-effects is hard to reason about because it gives us no guarantees about what is going to happen. When a program depends on things other than its arguments it is relying on hidden inputs that might not be available at all. We can’t safely predict what these programs will do or even reuse them elsewhere because they are tightly coupled to their context.

These programs are also hard to test in isolation without excessive mocking and stubbing.

Avoiding side-effects: pure functions

Pure functions depend only on their arguments and return a value without causing side-effects. These functions will always return the same output given a specific input. For example:

const incrementor = (count) => { count + 1; };

or

const myLogger = (output) => { "Log: " + output; };

This second function is not interacting with I/O (console) anymore. This pure function transforms data into a form that we can later send to the console. When testing this code, we don’t have to mock or assert against console.log.

Pure functions are easy to understand, test, change, compose, and reuse.

Avoiding side-effects: immutable data

We’re safe inside of pure functions because we know our inputs and outputs. But what if the inputs are mutable (i.e., something outside of the function can change them)?

const array = [3,1,2];
const getFirst = (array) => { return array[0]; };
getFirst(array) // 3

array.sort(); // somewhere else, something mutated the array
getFirst(array) // 1

Our function is working fine, but it’s giving us an unexpected output. JavaScript arrays and objects are mutable, so we have to be very careful. Languages like Clojure and Elixir have immutable data structures, so this problem doesn’t happen.

Freeze Data

For primitive types like strings and numbers, we can rely on const because the values are immutable. Reassigning the variable name will raise an error.

const myName = "Ahmed";
myName = "Paula" // TypeError: Assignment to constant variable.

For arrays and objects we can use Object.freeze(). Going back to the example above:

const array = Object.freeze([3,1,2]);
const getFirst = (array) => { return array[0]; };
getFirst(array) // 3

array.sort(); // TypeError: Cannot assign to read only property '1' ...
getFirst(array) // 3

This technique doesn’t work for nested data types; for those we have to freeze each level recursively. MDN has an example of doing this or you can use the deep-freeze npm package which does the same thing.

We can’t change immutable data types, so we have to make a copy. Copying does come with a performance penalty, especially if we’re transforming a lot of data, so we have to be cognizant of that.

In ES2015 we can copy arrays with the spread operator, like so: newArr = [ ...prevArr ] and in ES2009 we can use newArr = prevArr.slice(). For objects we can use the spread operator in a similar way nextObj = { ...prevObj } or with ES2009 nextObj = Object.assign({}, prevObj)

Note that the above examples only do a shallow copy. Another example from MDN gives us a solution for deep clone that works for both objects and arrays: nextObj = JSON.parse(JSON.stringify(prevObj)).

Languages like Clojure and Elixir that have immutable data structures take care of this for you and do it in a very efficient manner. The Wikipedia page about persistent data structures is a good read.

Dealing with side-effects

Functional programming is ideal for computations. The pure functions always apply the same algorithm to inputs and will always give a predictable output. The goal is to push side-effects (persisting state, writing to disk, printing on a screen, making HTTP requests, raising errors, etc.) to the edges of our program.

One potential way of dealing with side-effects is to treat them as streams. The “outside world” is a stream you can read from or write to. Pure functions are composed and then finally at the edges of our program we connect to the outside “streams.”

Input |> (function_1 |> function_2 |> function_3) |> Output

Everything inside the parentheses is pure functional programming. |> is a pipe operator that unfortunately JavaScript doesn’t have. But there’s a proposal.

Reactive programming might be useful for dealing with asynchronous data streams. In the reactive paradigm, user interactions with a UI are an event stream.

A pragmatic approach might be to “eject” from the pure functional paradigm and go into Object Oriented Programming at the edge of the program. OOP is complementary to the functional paradigm because it can be used to rationally model the side-effects of our programs and hide their implementation details.