DEV Community

Avi Aryan
Avi Aryan

Posted on

Introduction to Functional Programming in JavaScript

Functional Programming is a paradigm of building computer programs using expressions and functions without mutating state and data.

By respecting these restrictions, functional programming aims to write code that is clearer to understand and bug resistant. This is achieved by avoiding using flow-control statements (for, while, break, continue, goto) which make the code harder to follow. Also, functional programming requires us to write pure, deterministic functions which are less likely to be buggy.

In this article, we will talk about doing functional programming using JavaScript. We will also explore various JavaScript methods and features that make it possible. In the end, we will explore different concepts associated with functional programming and see why they are so powerful.

Before getting into Functional Programming though, one needs to understand the difference between pure and impure functions.

Pure vs Impure functions

Pure functions take some input and give a fixed output. Also, they cause no side effects in the outside world.

const add = (a, b) => a + b;

Here, add is a pure function. This is because, for a fixed value of a and b, the output will always be the same.

const SECRET = 42;  
const getId = (a) => SECRET * a;

getId is not a pure function. The reason being that it uses the global variable SECRET for computing the output. If SECRET were to change, the getId function will return a different value for the same input. Thus, it is not a pure function.

let id_count = 0;
const getId = () => ++id_count;

This is also an impure function, and that too for a couple of reasons - (1) It uses a non-local variable for computing its output. (2) It creates a side effect in the outside world by modifying a variable in that world.

getId impure illustration

This can be troublesome if we had to debug this code.

What’s the current value of id_count? Which other functions are modifying id_count? Are there other functions relying on id_count?

Because of these reasons, we only use pure functions in functional programming.

Another benefit of pure functions is that they can be parallelized and memoized. Have a look at the previous two functions. It’s impossible to parallelize or memoize them. This helps in creating performant code.

The Tenets of Functional Programming

So far, we have learned that functional programming is dependant on a few rules. They are as follows.

  • Don’t mutate data
  • Use pure functions
    • Fixed output for fixed inputs
    • No side effects
  • Use expressions and declarations

When we satisfy these conditions, we can say our code is functional.

Functional Programming in JavaScript

JavaScript already has some functions that enable functional programming. Example - String.prototype.slice, Array.protoype.filter, Array.prototype.join.

On the other hand, Array.prototype.forEach, Array.prototype.push are impure functions.

One can argue that Array.prototype.forEach is not an impure function by design but think about it, it’s not possible to do anything with it except mutating non-local data or doing side effects. Thus it’s okay to put it in the category of impure functions.

Also, JavaScript has const declaration which is perfect for functional programming since we won’t be mutating any data.

Pure functions in JavaScript

Let’s look at some of the pure functions (methods) given by JavaScript.

filter

As the name suggests, this filters the array.

array.filter(condition);

The condition here is a function that gets each item of the array and it should decide whether to keep the item or not and return the truthy boolean value for that.

const filterEven = x => x%2 === 0;  
[1, 2, 3].filter(filterEven);  
// [2]

Notice that filterEven is a pure function. If it had been impure, then it would have made the entire filter call impure.

map

map maps each item of array to a function and creates a new array based on the return values of the function calls.

array.map(mapper)

mapper is a function that takes an item of an array as input and returns the output.

const double = x => 2 * x;  
[1, 2, 3].map(double);  
// [2, 4, 6]

reduce

reduce reduces the array to a single value.

array.reduce(reducer);

reducer is a function that takes the accumulated value and the next item in the array and returns the new value. It is called like this for all values in the array, one after another.

const sum = (accumulatedSum, arrayItem) => accumulatedSum + arrayItem  
[1, 2, 3].reduce(sum);
// 6

reduce call illustration

concat

concat method adds new items to an existing array to create a new array. It’s different from push() in the sense that push() mutates data which makes it impure.

[1, 2].concat([3, 4])  
// [1, 2, 3, 4]

You can also do the same using the spread operator.

[1, 2, ...[3, 4]]

Object.assign

Object.assign method copies values from the provided object to a new object. Since Functional Programming is predicated on immutable data, we use it to make new objects based on existing objects.

const obj = {a : 2};  
const newObj = Object.assign({}, obj);  
newObj.a = 3;  
obj.a;  
// 2

With the advent of ES6, this can also be done using the spread operator.

const newObj = {...obj};

Creating your own pure function

We can create our pure function as well. Let’s do one for duplicating a string n number of times.

const duplicate = (str, n) =>  
    n < 1 ? '' : str + duplicate(str, n-1);

This function duplicates a string n times and returns a new string.

duplicate('hooray!', 3)  
// hooray!hooray!hooray!

Higher-Order Functions

Higher-Order Functions are functions that accept a function as an argument and returns a function. Often, they are used to add to the functionality of a function.

const withLog = (fn) => {  
    return (...args) => {  
        console.log({% raw %}`calling ${fn.name}`{% endraw %});  
        return fn(...args);  
    };  
};

In the above example, we create a withLog higher-order function that takes a function and returns a function that logs a message before the wrapped function runs.

const add = (a, b) => a + b;  
const addWithLogging = withLog(add);  
addWithLogging(3, 4);  
// calling add  
// 7

withLog HOF can be used with other functions as well and it works without any conflicts or writing extra code. This is the beauty of a HOF.

const addWithLogging = withLog(add);  
const hype = s => s + '!!!';  
const hypeWithLogging = withLog(hype);  
hypeWithLogging('Sale');  
// calling hype  
// Sale!!!

One can also call it without defining a combining function.

withLog(hype)('Sale');  
// calling hype
// Sale!!!

Currying

Currying means breaking down a function that takes multiple arguments into one or multiple levels of higher-order functions.

Let’s take the add function.

const add = (a, b) => a + b;

When we are to curry it, we rewrite it distributing arguments into multiple levels as follows.

const add = a => {
    return b => {
        return a + b;
    };
};
add(3)(4);  
// 7

The benefit of currying is memoization. We can now memoize certain arguments in a function call so that they can be reused later without duplication and re-computation.

// assume getOffsetNumer() call is expensive
const addOffset = add(getOffsetNumber());
addOffset(4);
// 4 + getOffsetNumber()
addOffset(6);

This is certainly better than using both arguments everywhere.

// (X) DON"T DO THIS  
add(4, getOffsetNumber());  
add(6, getOffsetNumber());  
add(10, getOffsetNumber());

We can also reformat our curried function to look succinct. This is because each level of the currying function call is a single line return statement. Therefore, we can use arrow functions in ES6 to refactor it as follows.

const add = a => b => a + b;

Composition

In mathematics, composition is defined as passing the output of one function into input of another so as to create a combined output. The same is possible in functional programming since we are using pure functions.

To show an example, let’s create some functions.

First function is range which takes a starting number a and an ending number b and creates an array consisting of numbers from a to b.

const range = (a, b) => a > b ? [] : [a, ...range(a+1, b)];

Then we have a function multiply that takes an array and multiplies all the numbers in it.

const multiply = arr => arr.reduce((p, a) => p * a);

We will use these functions together to calculate factorial.

const factorial = n => multiply(range(1, n));  
factorial(5);  
// 120  
factorial(6);  
// 720

The above function for calculating factorial is similar to f(x) = g(h(x)) thus demonstrating the composition property.

Concluding Words

We went through what are pure and impure functions, what is functional programming, what are the new JavaScript features that help with it and what are a few key concepts in functional programming.

We hope that this piece piques your interest in functional programming and possibly motivates you to try it in your code. We are positive that it will be a learning experience and a milestone in your software development journey.

Functional Programming is a well-researched and robust paradigm of writing computer programs. With the introduction of ES6, JavaScript allows for a much better functional programming experience than ever before.


This article was first posted on Toptal's Engineering Blog

Top comments (0)