What is Functional Programming?
Functional programming is a style of programming that uses functions and their application rather than lists of commands that are used in imperative programming languages.
It’s a more abstract style of programming that has its roots in mathematics — in particular, a branch of mathematics known as Lambda Calculus, which was devised by the mathematician Alonzo Church in 1936 as a formal model for computability. It’s made up of expressions and functions that map one expression to another. Fundamentally, this is what we do in functional programming: we use functions to transform values into different values.
Haskell is a purely functional programming language that was developed in the 1990s, and is similar to Scala and Clojure. Using these languages, you’re forced to code in a functional style. Learning Haskel has given us a real appreciation of all the advantages that functional programming provides.
Why is Functional Programming So Good?
In short, functional programming languages often lead to code that’s concise, clear and elegant. The code is usually easier to test and can be applied in multi-threaded environments without any problems.
If you speak to a lot of different programmers, you’ll probably get an entirely different opinion about functional programming from each — ranging from those who absolutely detest it to those who absolutely love it. We (the authors of this article) sit on the “love it” end of the scale, but we totally appreciate that it’s not everybody’s cup of tea, especially because it’s a very different approach from how programming is typically taught.
However, once you’ve got the hang of functional programming, and once the thought process has clicked, it becomes second nature and changes the way you write code.
Rule 1: Purify Your Functions
A key part of functional programming is to ensure that the functions you write are “pure”. If you’re new to this term, a pure function essentially satisfies the following conditions:
It has referential transparency. This means that, given the same arguments, the function will always return the same value. Any function call could be replaced with the return value and the program would still function in the same way.
It has no side-effects. This means that the function doesn’t make any changes outside the scope of the function. This can include changing global values, logging to the console, or updating the DOM.
Pure functions must have at least one parameter and must return a value. If you think about it, if they didn’t accept any arguments, they wouldn’t have any data to work with, and if they didn’t return a value, what would be the point of the function?
Pure functions may not appear totally necessary to begin with, but having impure functions can lead to whole changes in a program, leading to some serious logic errors!
let minimum = 21 const checkAge = age => age >= minimum const checkAge = age => const minimum = 21 return age >= minimum
In the impure function, the
checkAge function relies on the mutable variable
minimum. If, for example, the
minimum variable were to be updated later in the program, the
checkAge function might return a Boolean value with the same input.
Imagine if we run this:
checkAge(20) >> false
Now, let’s imagine that, later in the code, a
changeToUK() function updates the value of
minimum to 18.
Then, imagine we run this:
checkAge(20) >> true
Now the function
checkAge evaluates to different values, despite being given the same input.
Pure functions make your code more portable, since they don’t depend on any other values outside of the values provided as an arguments. The fact that the return values never change makes pure functions easier to test.
Consistently writing pure functions also removes the potential for mutations and side effects to occur.
To make your functions more portable, ensure that your functions always kept pure.
Rule 2: Keep Variables Constant
Declaring variables is one of the first things any programmer learns. It becomes trivial, but it’s immensely important when using a functional style of programming.
One of the key principles of functional programming is the idea that, once a variable has been set, it remains in that state for the whole of the program.
This is the simplest example of showing how reassignment/redeclaration of variables in code can be a disaster:
const n = 10 n = 11 TypeError: "Attempted to assign to readonly property."
If you think about it, the value of
n can’t simultaneously be
11; it doesn’t make logical sense.
A common coding practice in imperative programming is to increment values using the following code:
let x = 5 x = x + 1
In mathematics, the statement
x = x + 1 is illogical, because if you subtract
x from both sides you’ll be left with
0 = 1, which is clearly not true.
Rule 3: Use Arrow Functions
In mathematics, the concept of a function is one that maps a set of values to another set of values. The diagram below shows the function that maps the set of values on the left to the set of values on the right by squaring them:
This is how it would be written in maths with arrow notation:
f: x → x². This means that the function
f maps the value
We can use arrow functions to write this function almost identically:
const f = x => x**2
However, one of the hardest things to adapt to when using a functional style of programming is the mindset of every function being a mapping of an input to an output. There’s no such thing as a procedure. We’ve found using arrow functions helps us understand the process of functions much more.
Arrow functions have an implicit return value, which really helps visualize this mapping.
The structure of arrow functions — especially their implicit return value — helps to encourage the writing of pure functions, as they’re literally structured as “input maps to output”:
args => returnValue
Another thing we like to put an emphasis on, especially when writing arrow functions, is the use of ternary operators. If you’re unfamiliar with ternary operators, they’re an inline
if...else statement and of the form
condition ? value if true : value if false.
One of the main reasons for using ternary operators in functional programming is the necessity of the
else statement. The program must know what to do if the original condition isn’t satisfied. Haskell, for example, enforces an
else statement, and will return an error if one isn’t given.
Another reason for using ternary operators is that they’re expressions that always return a value, rather than
if-else statements that can be used to perform actions with potential side effects. This is particularly useful with arrow functions, because it means you can ensure there’s a return value and keep the image of mapping an input to an output. If you’re not sure about the subtle difference between statements and expressions, this guide on statements vs expressions is well worth a read.
To illustrate those two conditions, here’s an example of a simple arrow function that makes use of a ternary operator:
const action = state => state === "hungry" ? "eat cake" : "sleep"
action function will return a value of “eat” or “sleep” depending on the value of the
Therefore, to conclude: when making your code more functional, you should follow these two rules:
- write your functions using arrow notation
if...elsestatements with ternary operators
Rule 4: Remove For Loops
Given that using
for loops to write iterative code is so common in programming, it seems pretty odd to be saying to avoid them. In fact, when we first discovered that Haskell didn’t even have any kind of
for loop operation, we struggled to understand how some standard operations could even be achieved. However, there are some very good reasons why
for loops don’t appear in functional programming, and we soon found out that every type of iterative process can be achieved without using
The most important reason for not using
for loops is that they rely on mutable states. Let’s look at a simple
function sum(n) let k = 0 for(let i = 1; i < n+1; i++) k = k + i return k sum(5) = 15
As you can see, we have to use a
let in the
for loop itself, and also for the variable we’re updating within the
As already explained, this is typically bad practice in functional programming, as all variables in functional programming should be immutable.
If we wanted to write the code where all the variables were immutable, we could use recursion:
const sum = n => n === 1 ? 1 : n + sum(n-1)
As you can see, no variable is ever updated.
The mathematicians among us will obviously know that all this code is unnecessary, because we can just use the nifty sum formula of
0.5*n*(n+1). But it’s a great way to illustrate the difference between the mutability of
for loops versus recursion.
For example, say we wanted to add 1 to every value in an array. Using an imperative approach and a
for loop, our function could look something like this:
function addOne(array) for (let i = 0; i < array.length; i++) array[i] = array[i] + 1 return array addOne([1,2,3]) === [2,3,4]
However, instead of the
map method and write a function that looks like this:
const addOne = array => array.map(x => x + 1)
If you’ve never met a
Haskell doesn’t have
Rule 5: Avoid Type Coercion
Haskell is a strongly typed language that requires type declarations. This means that, before any function, you need to specify the type of the data going in and the type of the data coming out, using the Hindley-Milner system.
add :: Integer -> Integer -> Integer add x y = x + y
This is a very simple function that adds two numbers together (
y). It may seem slightly ridiculous to have to explain to the program what the type of data is for every single function, including very simple ones like this, but ultimately it helps show how the function is intended to work and what it’s expected to return. This makes code much easier to debug, especially when it starts to get more complicated.
A type declaration follows the following structure:
functionName :: inputType(s) -> outputType
"Hello" + 5evaluates to
"Hello5", which isn’t consistent. If you want to concatenate a string with a numerical value, you should write
"Hello" + String(5).
ifstatement is the equivalent of
false. This can lead to lazy programming techniques, neglecting to check if numerical data is equal to
const even = n => !(n%2)
This is a function that evaluates whether or not a number is even. It uses the
! symbol to coerce the result of
n%2 ? into a Boolean value, but the result of
n%2 isn’t a Boolean value, but a number (either
Hacks like these, while looking clever and cutting down the amount of code you write, break the type consistency rules of functional programming. Therefore, the best way to write this function would be like so:
const even = n => n%2 === 0
For example, a
product function that multiplies all the numbers in an array together and returns the result could be written with the following type declaration comment:
const product = numbers => numbers.reduce((s,x) => x * s,1)
Here, the type declaration makes it clear that the input of the function is an array that contains elements of the type
Number, but it returns just a single number. The type declaration makes it clear what’s expected as the input and output of this function. Clearly this function wouldn’t work if the array didn’t consist of just numbers.
To summarize, here are the five rules that will help you achieve functional code:
- Keep your functions pure.
- Always declare variables and functions using const.
- Use arrow notation for functions.
- Avoid using
- Use type declaration comments and avoid type coercion shortcuts.
While these rules won’t guarantee that your code is purely functional, they’ll go a long way towards making it more functional and helping make it more concise, clear and easier to test.
We really hope these rules will help you as much as they’ve helped us! Both of us are massive fans of functional programming, and we would highly encourage any programmer to use it.