Benefits of Functional Programming by Example

Functional programming is a programming paradigm (technique) where functions are used together to return new values, rather than modifying variable values multiple times. Functions that always return the same result given the same inputs are said to be free of side effects, or pure. When code relies on changes to external variables, it can lose its purity because the same inputs no longer create the same outputs consistently. By finding purer ways to design programs, we can use functions in ways that won’t impact how other code works through side effects. This functional approach to programming often leads to more reusable, testable, and stable code.

Functional programming can be difficult to understand at first, especially due to its complex terminology. Rather than learning more theoretical aspects of functional programming, we’ll look at some examples of how to improve existing JavaScript code using functional programming (though these concepts can be applied to most languages).

Iterating with forEach

for loops are commonly used to iterate over collections in many languages including JavaScript. Let’s say we have an array of values we need to display in alerts individually.

const beatles = ['John', 'Paul', 'George', 'Ringo']
for (const beatle of beatles) {
alert(beatle)
}

This example can be simplified further with a newer JavaScript function called forEach.

const beatles = ['John', 'Paul', 'George', 'Ringo']
beatles.forEach(beatle => alert(beatle))

Using forEach we can pass a function called an iteratee, which is called with each value of the array, giving us the same result. forEach is called a high order function because it takes a function as an argument. This is a powerful functional programming technique for composing functionality with small, simple functions. Because alert is a function and functions are also values in JavaScript, we can refer to the existing alert function instead of wrapping it in a new function.

const beatles = ['John', 'Paul', 'George', 'Ringo']
beatles.forEach(alert)

Creating new arrays with map

One of the most common use cases for iterating over an array is modifying each item of the array in place. For example, let’s take an array of numbers and round them individually. We will also need to keep track of the current array index.

const numbers = [1.4, 2.6, 3.14]
let index = 0
for (const number of numbers) {
numbers[index] = Math.round(number)
index++
}
numbers // [1, 3, 3]

numbers is in the preferred format, but what if we’re only formatting numbers to make them easier to read, and we still need the decimal places for precise calculations? We’ve lost precision of our numbers, and any math code relying on the same array may have less accurate results. This is an unwanted side effect of rounding numbers in a for loop. Instead, we can take a more functional approach and create a new array instead of modifying the values in the original array, preventing subtle bugs and promoting reuse.

const numbers = [1.4, 2.6, 3.14]
const rounded = []
for (const number of numbers) {
rounded.push(Math.round(number))
}
rounded // [1, 3, 3]

This is safer, but the nature of for loops still requires the side effect of pushing to the new rounded array. What if we could iterate over the array like with forEach, but create a new array by applying Math.round to every value? Fortunately, there’s a similar function called map which returns a new array based on the return values of the given function.

const numbers = [1.4, 2.6, 3.14]
const rounded = numbers.map(Math.round) // [1, 3, 3]

This gives us the result we want without modifying a temporary array variable. We could even replace Math.round with a different pure function, and the original numbers would remain the same. These functional changes have added a lot of safety and reusability to our code, without requiring us to write in a different functional programming language.

Creating new values with reduce

The map function is handy for updating array values individually, but we often need to calculate a value that isn’t an array of the same length as the original. For example, let’s add numbers in an array together.

let sum = 0
const numbers = [1, 2, 3, 4]
numbers.forEach(number => sum += number)
sum // 10

We need to modify our sum here to add each number to it, but there’s a more pure way to combine these values without creating side effects in a temporary sum variable. Like the functions we’ve tried so far, reduce iterates over each value of an array, except it lets us build any new value (not just an array) one step at a time. The reduce function works by passing a special value called an accumulator along with each value of the original array, which is replaced by the return value of our given function after iterating over each value.

const numbers = [1, 2, 3, 4]
const sum = numbers.reduce((accumulator, number) => accumulator + number) // 10

We can combine our numbers into a new accumulator value individually, without modifying the original array of numbers. Here our iteratee function is called a reducer because it takes two values (the previous accumulator and the current number) and reduces (combines) them into a single value, our new sum. The initial value of accumulator defaults to the first array value (1 in this case), so reduce will add each number in sequence to accumulator until we reach our final result. This initial value makes sense for a sum, but sometimes we need to change the initial value too. Let’s build a new array by doubling each of its numbers.

const numbers = [1, 2, 3, 4]
const doubled = numbers.reduce((accumulator, number) => accumulator.concat(number * 2), []) // [2, 4, 6, 8]

Along with our reducer function, we also pass add a second argument which overrides the initial accumulator to an empty array (instead of the first item, 1). This is necessary because you can’t call concat on a number, so we need to wrap the first number in an array like all the others. This end result is similar to map, which we just reimplemented ourselves by concatenating each number one at a time. map is an example of a function that can be replaced with reduce, but functional code is often easier to read with map.

const numbers = [1, 2, 3, 4]
const doubled = numbers.map(number => number * 2) // [2, 4, 6, 8]

Filtering arrays with filter

Let’s write a reducer that concatenates only even numbers into a new array.

const numbers = [1, 2, 3, 4]
const isEven = number => number % 2 === 0
const even = numbers.reduce((accumulator, number) => isEven(number) ? accumulator.concat(number) : accumulator, []) // [2, 4]

As powerful as reduce is, there are other simplified high order functions besides map. The filter function also iterates over array values, but instead of returning a new accumulator for each value, it only concatenates values into the new array if a certain condition is truthy. Let’s use filter to get even numbers from our array using our isEven function. isEven is an example of a predicate, a function that takes a value and returns a boolean.

const numbers = [1, 2, 3, 4]
const isEven = number => number % 2 === 0
const even = numbers.filter(isEven) // [2, 4]

Testing conditions with some and every

Sometimes we want to use predicates to test the values in existing arrays, without creating new arrays. some and every also take predicates, but they return true if our predicate is true for some (any) or every (all) values of the array respectively.

const numbers = [1, 2, 3, 4]
const isEven = number => number % 2 === 0
const someEven = numbers.some(isEven) // true
const everyEven = numbers.every(isEven) // false

We could alternatively use a for loop with new variables or even reduce, but these simpler functions clarify the intent of our code while avoiding the side effects of a for loop.

Function composition

Sometimes a more complex reducer can be simplified by calling multiple pure high order functions together. Let’s combine some of the concepts we reviewed and square all the even numbers in our array.

const numbers = [1, 2, 3, 4]
const isEven = number => number % 2 === 0
const square = number => Math.pow(number, 2)
const evenSquared = numbers.reduce((accumulator, number) => isEven(number) ? accumulator.concat(square(number)) : accumulator, []) // [4, 16]

Our reducer is combining two concepts: filtering if number is even and mapping number to its square. This sounds a lot like what filter and map already do, but because they receive and return new arrays, we can use them together. Combining smaller functions together to build bigger functions is called function composition.

const numbers = [1, 2, 3, 4]
const isEven = number => number % 2 === 0
const square = number => Math.pow(number, 2)
const evenSquared = numbers.filter(isEven).map(square) // [4, 16]

Our newer functional code is easier to read, while still taking advantage of our isEven predicate and our square iteratee. Like any effective software design practice, functional programming lets us separate functionality into simpler units of code (like our functions) that are easier to reuse and test in isolation.