In functional programming, there are a lot of ideas and patterns that can make writing code a lot easier — most importantly pure functions, immutable state, and higher-order functions. I want to spend some time discussing these concepts and why they’re helpful in the context of functional programming.
If you read with my last post, you’ll know that pure functions are really straightforward. Wikipedia defines them as “functions with two properties.” First, given the same arguments, they return the same value. And second, they have no side-effects.
Essentially, a pure function is just a single function that embodies the ideas of functional programming. (As a quick aside, any function that isn’t pure can be called an impure function.)
In a real-world situation, not all functions are pure. Ideally, you’ll compose as many pure functions as you can. Then you can push the impure functions to the edge of your application.
Immutable state is so valuable to functional programming that it’s sometimes mistaken as a piece of functional programming. As the name suggests, immutable state emphasizes avoiding the mutation of state in your programs.
To clarify that concept (along with a small nuance that I missed when I first learned this), let’s take a look at a couple of examples. In the first example, the call to
push mutates the referenced array. In the second example, the call to
concat returns a new array with the new element, instead of mutating the original value.
// Mutable State var arr = [1, 2, 3]; arr.push(4); // arr is now [1, 2, 3, 4] // Immutable State var arr1 = [1, 2, 3]; var arr2 = [1, 1, 1]; arr2 = arr1.concat(4); // arr1 is still [1, 2, 3] // arr2 is now [1, 2, 3, 4]
The nuance that I originally missed is that replacing a value with a new one is not mutation. So when we replace
arr2 with a new array, we’re not mutating
Immutable state is really valuable to functional programming because we want to avoid side effects. The easiest way to get an impure function is by utilizing state mutation.
When you use immutable state, assignment operators identify the only place values can change. Without immutable state, you can end up with functions that secretly modify their arguments and spend hours trying to chase them down. Programming with immutable state makes it a lot easier to fall into a functional programming mindset.
The phrase Higher-order functions (or HOF) sounds intimidating, but they’re crucial to building up abstractions as you build larger programs. Simply put, a higher-order function can be defined as one that accepts one or more functions as an argument. This is useful when working with lists of data or other data structures you can iterate.
I’m going to show some examples of how higher-order functions work. If you’re already familiar with
filter, this should just be review for you.
map function accepts two arguments, a list and a function that accepts one argument. <
map takes the list, calls the function with each element in the list, then returns a new list with the results of those calls.
const addFive = (x) => x + 5 const nums = [1, 2, 3, 4, 5]; const numsPlusFive = Array.map(nums, addFive); // numsPlusFive is now [6, 7, 8, 9, 10]
map is a great building block when operating on lists. It lets you think about what you want to do with each element in the list instead of thinking about the list as a whole.
Reduce accepts a list of items and then reduces that list down to a single item. Let’s use it to sum a list of numbers.
reduce accepts three arguments, a list of items, a function that accepts two arguments, and a starting value. It will call your function with the results of the last call and the next value in the list. The first value is called with the starting value you passed in. The value returned is from the last call of your function:
const add = (x, y) => x + y; const nums = [0, 5, 10, 20]; const sum = Array.reduce(nums, add, 0); // sum is now 35
reduce offers a similar benefit to
map, in that it lets you think about how to operate a unit of the list instead of the list as a whole. It can even be applied to convert types. One example might be taking a list of numbers and reducing it into a string of those numbers as characters.
The last higher-order function I wanted to touch on is
filter. As the name implies, it takes a list of items and filters out any that don’t meet a set criteria.
filter accepts two arguments, a list of items, and a function that accepts one argument and returns a Boolean. The list returned includes items that returned true when the function was called on them.
Here is a simple example that combines
const addFive = (x) => x + 5 const isEven = (x) => (x % 2) == 0 const nums = [1, 2, 3, 4, 5]; const numsPlusFive = Array.map(nums, addFive); const evenNums = Array.Filter(numsPlusFive, isEven); // evenNums is now [6, 8, 10]
This example shows why these functions are powerful. Individually, they might only do one thing, but they’re easy to compose. This ease of composition comes from their predictable return values. It’s much simpler to think about a series of small changes on a list than one large change.
Lists in Functional Programming
Before I finish talking about HOF, I wanted to touch on the importance of lists in functional programming. There are a lot of tools for manipulating lists in functional programming. Structuring your domain’s data as lists presents advantages when programming functionally. Some functional languages blur the line between what is a list of instructions and what’s a list of data.
This is the second post in a series on Functional Programming: