This article was first published on 20th January 2017 on the BCS website.
As professional developers it is our job to produce efficient, reliable code within a reasonable amount of time. Of course, the definition of ‘a reasonable amount of time’ will vary from project to project, but it usually means ‘as fast as possible’. How efficient your code needs to be depends upon it’s intention. The efficiency required of a real-time service like a financial information feed to a trading desk will be orders of magnitude higher than that required when updating your account password. What generally remains common across all project types is the concept of reliability: reliable code is code that continues to work no matter what it is doing and regardless of how many changes are made to it.
Even small code-bases can be hard to maintain or enhance if you don’t construct them well in the first instance. There can’t be many of us who haven’t returned to our own code 6 months after we first wrote it and made a small change only to find that it is now broken for no obvious reason. As for working on someone else’s code, well, don’t get me started. The larger the codebase, the more likely this is to happen. How, then, do we avoid wasting time debugging brittle code?
There are many techniques that could be used but the one I want to introduce here is functional programming (FP). This is a way of coding that aims to produce modular, robust and expressive code that is easy to reason about. Like many such techniques it is not a new idea (Haskell, for example, is a pure functional programming language that was defined in 1990). Fortunately it is not difficult to do and can be used in both object-oriented (OO) and non-OO code. It does, however, require us to think in a different way and to give up some old habits.
- is the language I use every day
- has great features that suit FP very well (e.g. functions as objects, meaning that functions can be passed as parameters to other functions)
- has features with really bad side-effects (e.g. global scope) that can be mitigated by FP.
So what is FP?
- Functional programming is a technique that uses functions to abstract flows and operations on data so as to avoid unwanted side-effects caused by mutations of state.
Declarative vs Imperative
Consider a humble for loop:
The style of programming where we use
for loops is imperative, meaning that it tells the computer in great detail exactly how to perform a task (in this case, outputting a number to the console). This has the side-effect of greatly reducing re-use, because imperative programming is targeted at solving a particular problem efficiently. This means that it runs at a far lower level of abstraction than functional code. The lower the level of abstraction, the lower the probability of reuse, and the greater the complexity and likelihood of errors.
FP, by contrast, uses a declarative programming style, meaning that it separates the program description from it’s evaluation. It regards programs as the evaluation of independent, pure functions. This raises the level of abstraction and allows us to concentrate on what is being done, and not on how it is being done. A much better FP option is to use
Array.forEach() to manage the looping, leaving us to concentrate on applying the appropriate behaviour to each element:
A useful side-effect of using
Array.forEach() is that we have produced a function which is idempotent (that is to say, a function that is guaranteed to produce the same result for a given input no matter how many times it is called). This greatly increases the stability and predictability of the code.
We can even use ES6 arrow functions (also known as lambda functions) to make this definition more succinct:
As with most things in programming, there may be good reasons for using a
for loop and not
Array.forEach() but you can now make a reasoned choice between these two options.
Pure vs Impure
for loop used in our previous may seem innocuous enough, but if you look carefully you’ll notice that I didn’t explicitly define
i (with a
var statement or with the preferable
let statement in ES6) which means that we actually have no way of knowing exactly what this loop will do - it all depends where we defined
Not only that, but the array over which it is iterating is not local to the loop’s scope, meaning that it, too is vulnerable to unwanted change. In short, our code example is impure. The
Array.forEach() method, on the other hand, is an example of a pure function, meaning that:
- it depends only on the input provided and not on any externally defined or hidden state that may be changed at any time during its evaluation or between evaluations.
- it doesn’t impose changes beyond its own scope such as modifying an input parameter passed by reference
Immutable vs mutable
Take the example of sorting an array:
Although this may look safe, it isn’t. The reason is that
Array.sort() sorts the array in place (i.e. you provide an array and it returns the same array in sorted order). To be an immutable function
Array.sort() would have to return a new array in sorted order and leave the original array untouched.
FP is based on the concepts of:
- Declarative programming
- Pure functions
A Simple Example
Let’s take a simple imperative program and convert it to a functional program to test the contention that it is easier to reason about the FP version. Consider an operation that takes a list of strings and normalises them (i.e. starts each string with a capital letter and removes any underscores that may be present). A declarative implementation could be as follows:
Does this look familiar? Now let us redefine this using FP. We will use
Array.filter(), which is a function that operates over an array of values and applies the supplied test to them, returning a new array containing the elements of the original array for which the test returns
true. The code now becomes:
The FP solution has re-factored some of the declarative code into separate functions. The
normalize functions could easily have been used in the declarative version (but, let’s be honest, probably wouldn’t have been). The ‘capitalizeSentence` function introduces the concept of recursion (in which a function calls itself until it meets a specific condition) to avoid explicitly looping over an array. Recursion is a very useful FP technique. All of these functions are pure and can be re-used. It is now much easier to see that the sequence is to check that a sentence is valid, normalize it by removing underscores and then capitalize each word in the sentence.
This post barely scratches the surface of functional programming techniques. There are plenty of other resources available on the web and in print that will take you deeper into the world of FP. What I have done is to define FP as being a programming technique that embodies the principles of pure, declarative, immutable coding. These principles are desireable because they encourage the creation of code which is free of unwanted side-effects and easy to reason about, leading to greater reliability. I hope that this will inspire you to look further and to adopt FP (where appropriate!) in your own coding practise.