Blast from the Past / Back to Math — re# studio weekly lunch series

Here at re# studio, we have a training program as part of the onboarding process. One aspect of the program is a weekly lunch discussion, to which all employees — old and new — are invited. For this week’s training topic, we continued our discussion of Functional Programming. Being a programmer, of course I’d heard the term “functional programming” before, so I came to the lunch meeting fully prepared to talk about Pure Functions, Functions as First-Class Citizens, Higher Order Functions, etc. but I did not expect to return to 7th grade Algebra.

As it turns out, there’s overlap between the functions we talk about in math class and those we write in code. Specifically, that which we refer to as a pure function in our Functional Programming paradigm aligns with our 7th grade Algebra function. In other words, not all Javascript functions are pure functions, but those that are, are comparable to functions as defined in mathematics.

Functions in Math

What makes a function a function? In mathematics, a function is a relation between a set of inputs and outputs where each input correlates to exactly one output.

As an example, f(x)= 2x is a function.

As you can see, each input x maps to exactly one output f(x).

On the other hand, f(x)=√x is not a function because for many given inputs (any input x > 0), there is more than one possible output.

You might recall from Algebra class using the Vertical Line Test, a method for determining whether graphs represent functions, whereby you place a pencil vertically over the graph in question and scan from left to right. If at any point in the scan, the pencil crosses the graph more than once, this is NOT A FUNCTION. A failed vertical line test is indicative of an input or set of inputs that correspond to more than one output.

Functions in Code

Similarly, pure functions must abide by the same stipulation: Every input must correspond to exactly one output. (Note that the reverse need not be true — there is no horizontal line test, after all.)

Here is an example of a pure function:

(The for loop is there only to show the results of the multiple calls to the function.)

function pure(num){
return Math.floor(Math.sqrt(num)) + 5
for (let i = 1; i<10; i++) {
setTimeout(() => { console.log(pure(8)) }, 10000 * i)

// 7
// 7
// 7
// 7
// 7

As you can see, an input of 8 to our ‘pure’ function will always yield 7.

On the other hand, we have this impure function:

function impure(index){
for (let i = 1; i<6; i++) {
setTimeout(() => { console.log(impure(8)) }, 10000 * i)
// 4
// 5
// 6
// 7
// 8

Here, the input 8 corresponds to different output values (depending, in this case, on the time).

In addition to the stipulation that every input maps to exactly one output, a pure function:

  • does not have side effects (i.e. does not mutate or rely on external state)

To understand the concept of side effects, let’s take a look at the difference between the slice (pure) and the splice (impure) methods that exist on the Javascript array class.

const _ = require(‘lodash’)
const test = require(‘ava’)
const numbers = [1, 2, 3, 4, 5]
test(‘Array slice is a pure function’, t => {
const numbersBefore = _.merge([], numbers)
numbers.slice(0, 2)
t.deepEqual(numbers, numbersBefore)
test(‘Array splice is a pure function’, t => {
const numbersBefore = _.merge([], numbers)
numbers.splice(0, 2)
t.deepEqual(numbers, numbersBefore)

These two assertions are testing that Array.prototype.slice and Array.prototype.splice do not mutate the array on which they operate. The first test passes because the slice method returns a new array that is a slice of the original array; the second test fails because the splice method operates in place, mutating the original array. As you can see, the slice method does not have side effects, but the splice method does.

In this array example, you can think of “the array on which they operate” as external state. If we stick to using pure functions, we will avoid mutating external state.

Benefits of pure functions

  1. caching: If we are employing pure functions, we can use a method called memoization to store results from previous function calls.

Let’s look at an example:

function fibonacci(idx){
if (idx === 1 || idx === 0){ return idx}
return fibonacci(idx - 1) + fibonacci(idx - 2)

In this classic implementation of fibonacci, our code gets slow quickly. (Due to the double recursive call, the bigO time complexity is 2^n.) That’s in part because we make recursive calls with the same number over and over. Check this out:

Note: this is only part of the stack!

We are calling fib with 5 five times! How inefficient! What if instead we only computed fib(5) once, stored the value in an object (our “memo”), and then referred to that value (instead of recomputing) the next time we call fib(5)?

Like so…

function fibMemoized(idx) {
const memo = { }
const fib = (number) => {
if (memo[number]) return memo[number]
if (number === 1 || number === 0){
return number
const result = fib(number — 1) + fib(number — 2)
memo[number] = result
return result
return fib(idx)

This memoized version is much faster because we don’t have to recompute recursive calls…

Red Xs indicate recursive calls that are already stored

…but this sort of optimization is only possible if the functions we are “memoizing” are pure. If we can’t be sure that a function returns the same thing every time it’s called with the same input, how can we rely on our stored function calls?

2. avoid bugs: Writing pure functions helps us avoid situations where mutated external state affects (unbeknownst to us) other functionality in our code.

3. easier testing: If you’re reading this, you’re likely a programmer. If you’re a programmer, you’ve likely felt the sense of dread that can come with being assigned a ticket to write tests — as well as the sense of deep dejection that comes with seeing that ticket sit indefinitely in the “To Do” swimlane.

A lot of the pain that is associated with writing tests comes from the difficulty of the set-up. When you use pure functions, a lot of this difficulty disappears.

As explained by drboolean of Github, testing pure functions is simple because “We don’t have to mock a ‘real’ payment gateway or setup and assert the state of the world after each test. We simply give the function input and assert output.” (1*)

4. Dependency injection: Because we cannot mutate or rely on external state, we have to explicitly pass any variables we need into the function. This leads to more clarity about what a function is doing. It also means that if we need to rename a variable, we need only rename it where it is declared and not everywhere it is used.

I hope you now know a bit more about functional programming. Maybe next time you decide to close over a variable, you’ll think twice. If nothing else, you’ve gotten a refresher on some 7th grade math.

I’d like to end this post with poetry inspired by a slack conversation (with a programmer here at re# studio) regarding the benefits of functional programming:

Free verse by Seth

“Bro, I would love to be better at it [functional programming]

But I feel like once you get good at it

you would be so much more productive as a programmer

like I waste so much time tracking down stupid bugs that I’ve made for myself

and functional programming either works right or not at all

so you catch it earlier”