Getting going with functional programming in JavaScript
Functional programming has made a strong resurgence in recent years and for good reason too. Functional software breaks code down into composable deterministic functions, resulting in code that is often simple, elegant, and expressive. It can lead to code that is easier to understand and maintain, and straightforward to test. It's now cropping up more and more in both job adverts and interviews too.
It shouldn't therefore come as a surprise that many people are now looking to gain exposure to functional programming, however the often mathematical notation can be intimidating to many.
In this article, I will throw some light on what makes code functional, and explain how you can get started reaping the benefits today.
The good news is, if you're using libraries like React and Redux, chances are you are already making some use of some functional patterns without even realising. Redux reducer functions are free of side effects and therefore pure. But more on that later.
As stated previously, functional programming breaks code down into composable deterministic functions. Let's take a look at what that actually means:
A highly composable system provides components that can be selected and assembled in various combinations to satisfy specific user requirements.
For functions to be composable, they must take one input and return one output. In this way, you're able to compose two or more such functions into a new function, by using the output of one as the input to the next.
A deterministic system is a system in which no randomness is involved in the development of future states of the system. A deterministic model will thus always produce the same output from a given starting condition or initial state.
For functions to be deterministic, they must not depend on any outside system state. Any attempt to interact with such state is known as a side effect. By only interacting with the function inputs and returning a single output, the function becomes deterministic. We refer to these kinds of functions as pure.
Pure functions are not only straightforward to understand but they're also easy to test, as no external system state needs to be set up beforehand. In contrast, with complex class based applications this setup can soon become a real headache.
So what does this actually look like? Let's break this down below, with two functions, double
and square
.
https://gist.github.com/d5d1f899da7a3ff0df62dca387ad2082
Notice how both functions take only one input and return only one output, making them both composable. Notice also how neither function relies on any external state or makes changes to external state, making them both deterministic, aka pure, and free of side effects.
Given they are composable, we can compose these two functions to form a new function. See below for an example of this:
https://gist.github.com/bcb7ed21388d2eddeae78026158ee0e3
Many utility libraries provide a compose
method, allowing us to rewrite the previous example in a nicer syntax:
https://gist.github.com/c5d487f4ecd946cfd3f9522e3534028f
As long as the compose function is curried (as in the example above), we can rewrite this using point free syntax like so:
https://gist.github.com/c40b10e03fc6a1637bc886430a768382
Pretty neat huh?! The next thing to realise is that any function can be made composable using a technique called currying. With currying, we transform a function with multiple arguments into a sequence of functions, each with a single argument. This is made possible by the inclusion of first class functions and closures within JavaScript. When a function is curried, the inner functions are closures, having access to their own scope and the scope of their outer functions, and in this way the transformed function essentially has the same behaviour.
Now that we've covered the cornerstones of functional programming, let's go over a few more basic principles, that will help to ensure your code is simple to reason about, painless to extend, and a joy to test. Ok, maybe it still won't be a joy to test, but it should be easier!
Immutable code is free from mutation, i.e. state is prevented from being changed. This makes code easier to understand, by removing the need to track back through your code to follow the various mutations of state, often holding several variables in your head at once. In doing so, immutable code reduces the cognitive load.
There are libraries available in JavaScript to help enforce this, but often just favouring const
over let
and var
and avoiding changing function inputs will go a long way towards giving you the benefits of this approach.
When following functional programming principles, recursion becomes essential. Recursion is when a function calls itself, and it provides an alternative to the non functional approach of using for loops with counters along with mutating state.
For example:
https://gist.github.com/71f3e6a5712a9e3e1853c78c35294955
Becomes:
https://gist.github.com/9efd4da39ab5ca13925dd16726e26d86
As mentioned a couple of times above, there are a variety of functional programming libraries in JavaScript, providing you with things like immutable data structures, and higher order functions for performing operations such as map, reduce, omit, compose, and many more.
A personal favourite of mine and one that is popular within the community is Ramda. Check out the Ramda API documentation for example usage.
After having let all this sink in, I hope you now have a better idea of what functional programming is, and feel confident enough to get started writing functional code today!
A few great resources on functional programming are: