Functional programming has become a really hot topic in the JavaScript world. Just a few years ago, few JavaScript programmers even knew what functional programming is, but every large application codebase I’ve seen in the past 3 years makes heavy use of functional programming ideas.
Functional programming (often abbreviated FP) is the process of building software by composing pure functions, avoiding shared state, mutable data, and side-effects. Functional programming is declarative rather than imperative, and application state flows through pure functions. Contrast with object oriented programming, where application state is usually shared and colocated with methods in objects.
Functional programming is a programming paradigm, meaning that it is a way of thinking about software
construction based on some fundamental, defining principles (listed above). Other examples of programming paradigms
include object oriented programming and procedural programming.
Functional code tends to be more concise, more predictable, and easier to test than imperative or object
oriented code — but if you’re unfamiliar with it and the common patterns associated with it, functional code can
also seem a lot more dense, and the related literature can be impenetrable to newcomers.
If you start googling functional programming terms, you’re going to quickly hit a brick wall of academic lingo
that can be very intimidating for beginners. To say it has a learning curve is a serious understatement. But if
you’ve been programming in JavaScript for a while, chances are good that you’ve used a lot of functional programming
concepts & utilities in your real software.
Don’t let all the new words scare you away. It’s a lot easier than it sounds.
The hardest part is wrapping your head around all the unfamiliar vocabulary. There are a lot of ideas in the
innocent looking definition above which all need to be understood before you can begin to grasp the meaning of
functional programming:
Pure functions
Function composition
Avoid shared state
Avoid mutating state
Avoid side effects
In other words, if you want to know what functional programming means in practice, you have to start with an
understanding of those core concepts.
A pure function is a function which:
Given the same inputs, always returns the same output, and
Has no side-effects
Pure functions have lots of properties that are important in functional programming, including referential
transparency (you can replace a function call with its resulting value without changing the meaning of the program).
Read “What is a Pure Function?” for more details.
Function composition is the process of combining two or more functions in order to produce a new function or
perform some computation. For example, the composition f . g (the dot means “composed with”) is equivalent to
f(g(x)) in JavaScript. Understanding function composition is an important step towards understanding how software is
constructed using the functional programming. Read “What is Function Composition?” for more.
Shared State
Shared state is any variable, object, or memory space that exists in a shared scope, or as the property of an object
being passed between scopes. A shared scope can include global scope or closure scopes. Often, in object oriented
programming, objects are shared between scopes by adding properties to other objects.
For example, a computer game might have a master game object, with characters and game items stored as
properties owned by that object. Functional programming avoids shared state — instead relying on immutable data
structures and pure calculations to derive new data from existing data. For more details on how functional software
might handle application state, see “10 Tips for Better Redux Architecture”.
The problem with shared state is that in order to understand the effects of a function, you have to know the
entire history of every shared variable that the function uses or affects.
Imagine you have a user object which needs saving. Your saveUser() function makes a request to an API on the
server. While that’s happening, the user changes their profile picture with updateAvatar() and triggers another
saveUser() request. On save, the server sends back a canonical user object that should replace whatever is in memory
in order to sync up with changes that happen on the server or in response to other API calls.
Unfortunately, the second response gets received before the first response, so when the first (now outdated)
response gets returned, the new profile pic gets wiped out in memory and replaced with the old one. This is an
example of a race condition — a very common bug associated with shared state.
Another common problem associated with shared state is that changing the order in which functions are called
can cause a cascade of failures because functions which act on shared state are timing dependent:
Timing dependency example
When you avoid shared state, the timing and order of function calls don’t change the result of calling the function.
With pure functions, given the same input, you’ll always get the same output. This makes function calls completely
independent of other function calls, which can radically simplify changes and refactoring. A change in one function,
or the timing of a function call won’t ripple out and break other parts of the program.
In the example above, we use Object.assign() and pass in an empty object as the first parameter to copy the
properties of x instead of mutating it in place. In this case, it would have been equivalent to simply create a new
object from scratch, without Object.assign(), but this is a common pattern in JavaScript to create copies of
existing state instead of using mutations, which we demonstrated in the first example.
If you look closely at the console.log() statements in this example, you should notice something I’ve
mentioned already: function composition. Recall from earlier, function composition looks like this: f(g(x)). In this
case, we replace f() and g() with x1() and x2() for the composition: x1 . x2.
Of course, if you change the order of the composition, the output will change. Order of operations still
matters. f(g(x)) is not always equal to g(f(x)), but what doesn’t matter anymore is what happens to variables
outside the function — and that’s a big deal. With impure functions, it’s impossible to fully understand what a
function does unless you know the entire history of every variable that the function uses or affects.
Remove function call timing dependency, and you eliminate an entire class of potential bugs.
Immutability
An immutable object is an object that can’t be modified after it’s created. Conversely, a mutable object is any
object which can be modified after it’s created.
Immutability is a central concept of functional programming because without it, the data flow in your program
is lossy. State history is abandoned, and strange bugs can creep into your software. For more on the significance of
immutability, see “The Dao of Immutability.”
In JavaScript, it’s important not to confuse const, with immutability. const creates a variable name binding
which can’t be reassigned after creation. const does not create immutable objects. You can’t change the object that
the binding refers to, but you can still change the properties of the object, which means that bindings created with
const are mutable, not immutable.
Immutable objects can’t be changed at all. You can make a value truly immutable by deep freezing the object.
JavaScript has a method that freezes an object one-level deep:
But frozen objects are only superficially immutable. For example, the following object is mutable:
As you can see, the top level primitive properties of a frozen object can’t change, but any property which is
also an object (including arrays, etc…) can still be mutated — so even frozen objects are not immutable unless you
walk the whole object tree and freeze every object property.
In many functional programming languages, there are special immutable data structures called trie data
structures (pronounced “tree”) which are effectively deep frozen — meaning that no property can change, regardless
of the level of the property in the object hierarchy.
Tries use structural sharing to share reference memory locations for all the parts of the object which are
unchanged after an object has been copied by an operator, which uses less memory, and enables significant
performance improvements for some kinds of operations.
For example, you can use identity comparisons at the root of an object tree for comparisons. If the identity
is the same, you don’t have to walk the whole tree checking for differences.
There are several libraries in JavaScript which take advantage of tries, including Immutable.js and Mori.
I have experimented with both, and tend to use Immutable.js in large projects that require significant amounts
of immutable state. For more on that, see “10 Tips for Better Redux Architecture”.
Explore the Series What is a Closure? What is the Difference Between Class and Prototypal Inheritance? What is a Pure Function? What is Function Composition? What is Functional Programming? What is a Promise? Soft Skills