As a programmer, you probably want to write elegant, maintainable, scalable, predictable code. The principles of functional programming, or FP, can significantly aid in these goals.
Functional programming is a paradigm, or style, that values immutability, first-class functions, referential transparency, and pure functions. If none of those words makes sense to you, don’t worry! We’re going to break down all this terminology in this article.
Functional programming evolved from lambda calculus, a mathematical system built around function abstraction and generalization. As a result, a lot of functional programming languages look very mathematical. Good news, though: you don’t need to use a functional programming language to bring functional programming principles to your code. In this post, we’ll use JavaScript, which has a lot of features that make it amenable to functional programming while not being tied to that paradigm.
The Core Principles of Functional Programming
Now that we’ve discussed what functional programming is, let’s talk about the core principles behind FP.
Pure functions
I like to think of functions as machines — they take an input, or arguments, and then output something, the return value. Pure functions don’t have ‘side effects’ or actions that don’t relate to the output of the function. Some potential side effects would be printing a value or logging it out with console.log
, or manipulating variables outside the function.
Here’s an example of an impure function:
let number = 2;
function squareNumber() {
number = number * number; // impure action: manipulating variable outside function
console.log(number); // impure action: console log-ing values
return number;
}
squareNumber();
The function below is pure. It takes an input and produces an output.
// pure function
function squareNumber(number) {
return number * number;
}
squareNumber(2);
Pure functions operate independently from state outside of the function, so they shouldn’t rely on global state, or variables outside of itself. In the first example, we use the number
variable created outside the function, and set it inside. This violates the principle. If you’re relying heavily on constantly changing global variables, your code will be unpredictable and hard to trace. It will be more difficult to figure out where bugs are happening and why values are changing. Instead, using only inputs, outputs, and variables local to functions allows for easier debugging.
In addition, functions should follow referential transparency, which means that given a certain input, their output will always be the same. In the above function, if I pass 2
to the function, it will return 4
always. The same is not true for API calls or generating random numbers, as two examples. Given the same input, the output may or may not be returned.
// Not referentially transparent
Math.random();
// 0.1406399143589343
Math.random();
// 0.26768924082159495ß
Immutability
Functional programming also prioritizes immutability, or not directly modifying data. Immutability leads to predictability — you know the values of your data, and they aren’t changing. It makes code simple, testable, and able to run on distributed and multi-threaded systems.
Immutability comes into play frequently when we work with data structures. Many array methods in JavaScript directly modify the array. For example, .pop()
directly removes an item from the end of the array and .splice()
allows you to take a section of an array. Instead, within the functional paradigm, we would copy the array and in that process remove the element we are looking to eliminate.
// We are mutating myArr directly
const myArr = [1, 2, 3];
myArr.pop();
// [1, 2]
// We are copying the array without the last element and storing it to a variable
let myArr = [1, 2, 3];
let myNewArr = myArr.slice(0, 2);
// [1, 2]
console.log(myArr);
First-class functions
In functional programming, our functions are first-class, which means we can use them like any other value. We can create arrays of functions, pass them as arguments to other functions, and store them in variables.
let myFunctionArr = [() => 1 + 2, () => console.log("hi"), x => 3 * x];
myFunctionArr[2](2); // 6
const myFunction = anotherFunction => anotherFunction(20);
const secondFunction = x => x * 10;
myFunction(secondFunction); // 200
Higher-order functions
Higher order functions are functions that do one of two things: they either take a function as one or more of its parameters, or they return a function. There are many of the first type of higher-order functions built into JavaScript — like map
, reduce
, and filter
which we can use to interact with arrays.
filter
is used to return a new array from an old one that contains only values that fit a condition, which we provide.
const myArr = [1, 2, 3, 4, 5];
const evens = myArr.filter(x => x % 2 === 0); // [2, 4]
map
is used to iterate through the items in an array, modifying each item according to the provided logic. In the below example, we double each item in an array by passing a function to map that multiplies our value by two.
const myArr = [1, 2, 3, 4, 5];
const doubled = myArr.map(i => i * 2); // [2, 4, 6, 8, 10]
reduce
allows us to output a single value based on an inputted array — it’s often used to sum an array, flatten arrays, or group values in some way.
const myArr = [1, 2, 3, 4, 5];
const sum = myArr.reduce((i, runningSum) => i + runningSum); // 15
You could also implement any of these yourself! For example, you could create a filter function like so:
const filter = (arr, condition) => {
const filteredArr = [];
for (let i = 0; i < arr.length; i++) {
if (condition(arr[i])) {
filteredArr.push(arr[i]);
}
}
return filteredArr;
};
The second type of higher-order function, functions that return other functions, are also a relatively frequent pattern. For example:
const createGreeting = greeting => person => `${greeting} ${person}`
const sayHi = createGreeting("Hi")
console.log(sayHi("Ali")) // "Hi Ali"
const sayHello = createGreeting("Hello")
console.log(sayHi("Ali")) // "Hello Ali"
Currying is a related technique that you may be interested in reading up on as well!
Function composition
Function composition is when you combine multiple simple functions in order to create more complex ones. So, you could have an averageArray
function that combines an average
function with a sum
function that sums up an array’s values. The individual functions are small and could be reused for other purposes, and in combination they perform a more complete task.
const sum = arr => arr.reduce((i, runningSum) => i + runningSum);
const average = (sum, count) => sum / count;
const averageArr = arr => average(sum(arr), arr.length);
Benefits
Functional programming leads to modular code. You have small functions that you can reuse over and over. Knowing the specific functionality of each function means pinpointing bugs and writing tests should be straightforward, especially since the function outputs should be predictable.
In addition, if you are trying to use multiple cores, you can distribute function calls across those cores, so it can lead to more computational efficiency.
How Can You Use Functional Programming?
You don’t need to move completely over to functional programming to incorporate all these ideas. You can even use many of the ideas well in combination with object-oriented programming, which is often thought of as its opponent.
React, for example, incorporates many functional principals like immutable state, but also used the class syntax primarily for years. It can also be implemented in nearly any programming language — you don’t need to write Clojure or Haskell, unless you really want to.
Functional programming principles can lead to positive results in your code, even if you’re not a purist.
Frequently Asked Questions about Functional Programming
What are the key principles of functional programming?
Functional programming is based on a few key principles. The first is immutability, which means that once a variable is set, it cannot be changed. This eliminates side effects and makes the code easier to follow. The second principle is pure functions, which means that a function’s output is solely determined by its input, without any hidden inputs or outputs. The third principle is first-class functions, which means that functions can be used as inputs or outputs to other functions. This allows for higher-order functions and can make the code more concise and easier to understand.
How does functional programming differ from procedural programming?
The main difference between functional and procedural programming is the way they handle data and state. In procedural programming, the program state is stored in variables and can be changed over time. In functional programming, the state is not changed, but rather new states are created from existing ones. This makes functional programming more predictable and easier to debug, as there are no side effects to worry about.
What are the benefits of functional programming?
Functional programming offers several benefits. It can make the code more readable and easier to understand, as it avoids side effects and mutable state. It can also make the code more reliable, as it encourages the use of pure functions that always produce the same output for the same input. Furthermore, functional programming can make the code easier to test and debug, as functions can be tested in isolation.
What are the challenges of functional programming?
While functional programming has many benefits, it also has some challenges. It can be difficult to learn, especially for those who are used to procedural or object-oriented programming. It can also be more difficult to implement certain algorithms in a functional style. Furthermore, functional programming can sometimes lead to less efficient code, as it often involves creating new objects instead of modifying existing ones.
What languages support functional programming?
Many programming languages support functional programming to some extent. Some languages, like Haskell and Erlang, are purely functional, while others, like JavaScript and Python, are multi-paradigm languages that support functional programming along with other paradigms. Even languages that are not traditionally associated with functional programming, like Java and C++, have added features in recent years to support functional programming.
How does functional programming handle side effects?
In functional programming, side effects are avoided as much as possible. This is achieved by using pure functions that do not change any state or perform any I/O operations. When side effects are necessary, they are isolated and controlled. For example, in Haskell, side effects are handled using monads, which encapsulate the side effects and provide a way to chain them together in a controlled manner.
What is a higher-order function in functional programming?
A higher-order function is a function that takes one or more functions as arguments, returns a function as its result, or both. Higher-order functions are a key feature of functional programming, as they allow for functions to be used as data. This can lead to more concise and expressive code.
What is recursion in functional programming?
Recursion is a technique where a function calls itself in its own definition. In functional programming, recursion is often used as a substitute for loops, as loops involve mutable state, which is avoided in functional programming. Recursion can be used to solve a wide range of problems, from calculating factorials to traversing trees.
What is currying in functional programming?
Currying is a technique in functional programming where a function with multiple arguments is transformed into a sequence of functions, each with a single argument. This allows for partial application of functions, where a function is applied to some of its arguments, and a new function is returned that takes the remaining arguments.
What is functional reactive programming?
Functional reactive programming (FRP) is a programming paradigm that combines functional programming and reactive programming. In FRP, the program state is modeled as a series of immutable values over time, and functions are used to transform and combine these values. This makes it easier to reason about asynchronous and event-driven programs, as it avoids mutable state and side effects.
Ali teaches people to code at welearncode.com, General Assembly, and the Ladybug Podcast. She loves Python, JavaScript, and making learning programming more friendly.