There's nothing new about functional programming. Functional programming has been around for much longer than OOP as it dates back to the 60s.
Functional Programming is a paradigm. It follows the declarative pattern, which means, it focuses on what the program wants to achieve without indicating how. Particularly, JavaScript cannot be compared with functional languages like Haskell, Erlang, Elm, etc., but it supports the functional paradigm very well. Being engaged in functional programming, we design the application architecture and write code using only functions. Functional programming is excellent for data processing, concurrent, high critical, and serverless systems.
In this article, we will consider the following concepts of Functional Programming in JavaScript with simple code examples:
- Pure functions;
- Immutability;
- Referential Transparency;
- First-class functions;
- Higher-order functions.
Why functional programming matters?
- Readable;
- Concise;
- Testable;
- Enables Concurrency;
- Robust.
In functional programming, small modules can be coded quickly and easily. It is one of the main advantages of this paradigm. Besides, Functional Programming is a lot more concise than OOP. It tends to enforce the writing of the code in order of operations, which is more logical. Functional Programming has better predictability with pure functions than with impure ones. Pure functions in functional programming are predictable because the only thing they depend on is their input, and the only thing they do is return their output. This means it's safer, and it's harder to introduce bugs in your code. Also, it means that it's easier to test code and find bugs if they occur.
Pure functions
The pure function returns the same result if given the same arguments. It does not look at anything else from the outside scope. It only returns its output. It does not have any other effect on the world. Pure functions do not make any other changes than returning their return value.
There is a notion in Functional Programming called side-effects. A side-effect is when a function not only returns a value but also goes off and changes something on the side. When a function runs, it could change something while it's running. You have to avoid side effects as much as possible. Sometimes you need side effects.
Always try to minimize the scope of every variable that you make. It means that every time you make a variable has it live in the smallest possible scope and localize your variables as much as possible.
Examples.
Not pure function:
const name = 'Alice' const sayHi = () => { console.log(`Hi ${name}`) }; sayHi();
Pure function:
const sayHi = (name) => `Hi ${name}`;sayHi('Alice');
Immutability
Immutability is a concept that has its roots in Functional Programming. Whenever we want to introduce changes to some data, we should get a new object back with the updated data, instead of modifying the original one. You might think of immutability as “save as” because it returns a new object while traditional in-place mutation would be like “save” — updating original and letting go of the earlier state. It gives stricter control over your data, immediately making your code safer and more predictable.
Primitives are only immutable values built into JavaScript. Consider the next example:
let string = "An immutable string"; let otherString = string.slice(8, 17);
Objects and arrays are mutable in JavaScript. It means that the data structure can be changed.
How to work with mutability in JavaScript?
To begin with, most array methods are used, such as sort, push, pop, splice are destructive. There are indeed few non-destructive methods available — a slice. It returns a piece of a given array without modifying the original one.
let fruits = ["Banana", "Orange", "Lemon", "Apple", "Mango"]; let citrus = fruits.slice(1, 3); console.log(citrus); // Outputs: Orange,Lemon console.log(fruits); // Outputs: Banana, Orange, Lemon, Apple, Mango
As you can see, the original array was preserved.
JS includes the spread operator. It provides an easy way to create a new array by copying over values from another array. The real power of spread operators comes from combining the spread syntax to make different operations like adding, removing, and updating.
let fruits = ["Banana", "Orange"]; let updatedFruits = […fruits, 'Mango']; // Outputs: Banana, Orange, Mango
In this example, I'm copying all the previous items, but I am also adding a new one all at once. The original array was not changed and I have an updated array. Besides slice, few other array methods also return a new object, instead of modifying the original one (filter, map, reduce, concat, etc.).
To make objects immutable, we need to freeze them. The Object.freeze() method freezes an object. Thus objects become immutable. Using this method, nothing can be added to or removed from the properties set of a frozen object. Any attempt to do so will fail.
const person = Object.freeze({ name: "John", lastName: "Dell" }); person.name = "Max"; //Outputs: Cannot assign to read-only property 'name'
We have to consider that using Object.freeze(), the nested objects can be changed. Object.freeze() does only a shallow freeze. To make an object immutable, we have to freeze deeper.
function deepFreeze(object) { var propNames = Object.getOwnPropertyNames(object); for (let name of propNames) { let value = object[name]; if (value && typeof value === "object") { deepFreeze(value); } } return Object.freeze(object); } var obj2 = { internal: { a: null } }; deepFreeze(obj2); obj2.internal.a = 'anotherValue'; // fails silently in non-strict mode obj2.internal.a; // null
Referential transparency
Referential transparency is another concept closely related to pure functions and side-effects. Referential Transparency means that functions do not rely on anything beyond their parameters, and consequence of this is that it becomes predictable in its behavior. It means that given the same parameters will return the same answer. It makes our code very testable.
First-class functions
If you are learning JavaScript, you might hear that in a language, functions are treated as first-class objects. This is because in JavaScript, as in other languages that support functional programming, functions are objects. A function of the first class can be an argument for another function, it can be assigned to a variable, in general, it can be handled just like any other object.
- Functions in JS can be assigned to variables:
const sayHi = () => { return 'Hi '; } console.log(sayHi()); // Outputs: Hi
- Functions in JS can be passed as arguments to a function:
const sayHiToSomeone = (callback, name) => { return callback() + ' ' + name; } console.log(sayHiToSomeone(sayHi, 'Max')); // Outputs: Hi Max
- Functions in JS can be returned from a function:
const greeterConstructor = greeting => { return country => { return greeting + ' ' + country; } } const engGreeter = greeterConstructor('Welcome to '); console.log(engGreeter('England.')); // Outputs: Welcome to England. const gerGreeter = greeterConstructor('Welkom Bij '); console.log(gerGreeter('Duits.')) // Outputs: Welkom Bij Duits.
Higher-order functions
Higher-order functions are functions that operate on other functions by either taking them as arguments or returning them.
A higher-order function is a major concept in Functional Programming. These functions allow you to write simpler and more elegant code. Using higher-order functions, we can reduce bugs and make our code easier to read and understand.
The fact that JavaScript supports first-class functions makes it possible to create higher-order functions. So, the concept of first-class functions explained how functions are treated in JavaScript and how we use them. The reason we use both of these concepts is it helps to understand what is possible and thus take advantage of the power of the JavaScript language. The most common application of higher-order functions in JavaScript is the callback. There are several methods in JavaScript that allow us to pass a function that will be used as part of that method. ES5 array methods illustrate this well. For example, the sort function on arrays is a higher-order function. The sort function takes a function as an argument or, with an anonymous function. Map, reduce, filter, forEach are other examples of higher-order functions built into JavaScript. So, always try to use the built-in higher-order functions in Javascript that we can all leverage to make our code more elegant instead of control loops like For.
let fruits = ["Banana", "Orange", "Apple", "Papaya", "Mango"]; let sorted = fruits.sort(); console.log(sorted); // Outputs: Apple,Banana,Mango,Orange,Papaya[1, 2, 3, 4].filter(function(n) { return n > 2 }) // Outputs: 3, 4
Conclusion
Functional Programming these days is becoming the standard of JavaScript application development and maintenance. Concepts from this article will allow you to take advantage of Functional Programming and write cleaner code. Also, in this article, I didn't include other Functional Programming concepts like recursion, monads, functors, lambda, monoid, curry, etc. They are quite advanced, so they were skipped in this article. Understanding these concepts of functional programming will definitely give you the upper hand.