In this article, we’ll explain a little bit about what functional programming is, and then go through five ways you can make your JavaScript more functional in style.
Key Takeaways
- Functional programming is a style of programming that uses functions and their application instead of lists of commands. It is more abstract and has its roots in mathematics. JavaScript is particularly suited to functional programming as functions are first-class objects.
- Pure functions are a key part of functional programming. They always return the same value given the same arguments and do not make changes outside the scope of the function. They make your code more portable and easier to test.
- In functional programming, variables should be kept constant. Once a variable has been set, it should remain in that state for the whole program. This is achieved by always declaring your variables using const.
- Arrow functions and ternary operators are recommended for functional programming in JavaScript. Arrow functions have an implicit return value, which helps visualize the mapping of input to output. Ternary operators are expressions that always return a value, making them useful for ensuring there’s a return value.
- For loops should be avoided in functional programming as they rely on mutable states. Instead, recursion and higher-order array methods should be used. Additionally, type coercion should be avoided to maintain type consistency. This can be achieved by writing type declaration comments before declaring a function.
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.
The authors of this article have fallen in love with functional programming over recent years. We started using JavaScript libraries that encouraged a more functional style and then jumped right into the deep end by learning how to code in Haskell.
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.
JavaScript is a multi-paradigm language, as it can used to program in an imperative, object-oriented, or functional style. It does lend itself particularly well to a functional style, though, as functions are first-class objects, which means they can be assigned to variables. It also means that functions can be passed as arguments to other functions (often known as callbacks) as well as being the return value of other functions. Functions that return other functions or accept them as parameters are known as higher-order functions, and they’re a fundamental part of functional programming.
Programming JavaScript in a functional style has become much more popular in recent years, particularly with the rise of React. React uses a declarative API that suits a functional approach, so having a solid understanding of the principles of functional programming will improve your React code.
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!
For example:
//impure
let minimum = 21
const checkAge = age => age >= minimum
//pure
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.
Mutations are a big red flag in functional programming, and if you want to find out more about why, you can read about them in A Guide to Variable Assignment and Mutation in JavaScript.
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 10
and 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.
For this reason, in Haskell you can’t assign a variable to one value and then reassign it to another value. To achieve this in JavaScript, you should follow the rule always declare your variables using const
.
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 x
to x²
.
We can use arrow functions to write this function almost identically:
const f = x => x**2
A key feature of using a functional style in JavaScript is using arrow functions as opposed to regular functions. Of course, this does really boil down to style, and using an arrow function over a regular function doesn’t actually affect how “functional” your code is.
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
.
You can read more about them in Quick Tip: How to Use the Ternary Operator in JavaScript.
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"
The action
function will return a value of “eat” or “sleep” depending on the value of the state
argument.
Therefore, to conclude: when making your code more functional, you should follow these two rules:
- write your functions using arrow notation
- replace
if...else
statements 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 for
loops.
The most important reason for not using for
loops is that they rely on mutable states. Let’s look at a simple sum
function:
function sum(n){
let k = 0
for(let i = 1; i < n+1; i++){
k = k + i
}
return k
}
sum(5) = 15 // 1 + 2 + 3 + 4 + 5
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 for
loop.
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.
Recursion isn’t the only solution to the mutability problem though, particularly when we’re dealing with arrays. JavaScript has a lot of inbuilt, higher-order array methods that loop over the values in an array without ever mutating any variables.
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 for
loop, we could make use of JavaScript’s inbuilt map
method and write a function that looks like this:
const addOne = array => array.map(x => x + 1)
If you’ve never met a map
function before, it’s definitely worth learning about them — along with all of JavaScript’s inbuilt higher-order array methods, such as filter
, especially if you’re really interested in functional programming in JavaScript. You can find more info about them in Immutable Array Methods: How to Write Remarkably Clean JavaScript Code.
Haskell doesn’t have for
loops at all. To make your JavaScript more functional, try to avoid using for loops by using recursion and the inbuilt, higher-order array methods instead.
Rule 5: Avoid Type Coercion
When programming in a language such as JavaScript that doesn’t require type declaration, it’s easy to forget about the importance of data types. The seven primitive data types used in JavaScript are:
- Number
- String
- Boolean
- Symbol
- BigInt
- Undefined
- Null
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.
For example:
add :: Integer -> Integer -> Integer
add x y = x + y
This is a very simple function that adds two numbers together (x
and 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
Type coercion can be a big problem when using JavaScript that has all sorts of hacks that can be used (or even abused) to get around data-type inconsistency. Here are the most common ones and how to avoid them:
- Concatenation.
"Hello" + 5
evaluates to"Hello5"
, which isn’t consistent. If you want to concatenate a string with a numerical value, you should write"Hello" + String(5)
. - Boolean statements and 0. In JavaScript, the value of
0
in anif
statement is the equivalent offalse
. This can lead to lazy programming techniques, neglecting to check if numerical data is equal to0
.
For example:
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 0
or 1
).
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:
// even :: Number -> Number
const even = n => n%2 === 0
Another important concept is to make sure that all data values in an array are of the same type. This isn’t enforced by JavaScript, but not having the same type can lead to problems when you want to use higher-order array methods.
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:
// product :: [ Number ] -> Number
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.
Haskell is a strongly typed language and JavaScript is weakly typed, but to make your JavaScript more functional you should write type declaration comments before declaring a function and ensure that you avoid type coercion shortcuts.
We should also mention here that you can obviously turn to TypeScript if you want a strongly typed alternative to JavaScript that will enforce type consistency for you.
Conclusion
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
for
loops. - 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.
If you’d like to dig further into functional JavaScript, we highly recommend reading Professor Frisby’s Mostly Adequate Guide to Functional Programming, which is available free online. And if you want go all the way and learn Haskell, we recommend using the Try Haskell interactive tutorial and reading the excellent Learn You A Haskell For Greater Good book that’s also free to read online.
FAQs About Functional Programming in JavaScript
Functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids changing state and mutable data. In JavaScript, it involves using functions as first-class citizens and avoiding side effects.
First-class functions in JavaScript means that functions are treated like any other variable. They can be assigned to variables, passed as arguments to other functions, and returned as values from other functions.
Immutability refers to the idea that once an object is created, it cannot be changed. In the context of functional programming in JavaScript, this means avoiding the modification of variables or data structures after they have been initialized.
Higher-order functions are functions that take other functions as arguments or return functions as results. They enable the composition of functions, making it easier to create modular and reusable code.
Yes, several libraries and frameworks, such as Ramda and lodash, provide utilities and functions that support functional programming concepts in JavaScript. They can help simplify and enhance functional programming practices.
Olivia Gibson is a student of Maths and Computer Science. Over the past year, she has immersed herself in the world of web development and loves coding in JavaScript and Python. Some of her highlights include, Numble and German Flashcards.
Darren loves building web apps and coding in JavaScript, Haskell and Ruby. He is the author of Learn to Code using JavaScript, JavaScript: Novice to Ninja and Jump Start Sinatra.He is also the creator of Nanny State, a tiny alternative to React. He can be found on Twitter @daz4126.