Functional Programming Concepts in JavaScript

John Au-Yeung - Jan 25 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

Functional programming is a programming paradigm which states that we create computation as the evaluation of functions and avoid changing state and mutable data.

In JavaScript, we can apply these principles to make our programs more robust and create fewer bugs.

In this article, we’ll look at how to apple some functional programming principles in our JavaScript programs, including pure functions and creating immutable objects.

Pure Functions

Pure functions are functions that always return the same output given the same set of inputs.

We should use pure functions as much as possible because they’re easier to test and the output is always predictable given a set of inputs.

Also, pure functions don’t create any side effects, which means that it does change anything outside the function.

An example of a pure function would be the following add function:

const add = (a, b) => a + b;
Enter fullscreen mode Exit fullscreen mode

This function is a pure function because if we pass in 2 numbers, we’ll get the 2 numbers added together returned.

It doesn’t do anything outside the function, and it doesn’t have values that change the returned value inside the function.

Example of functions that aren’t pure functions include:

let x;
const setX = val => x = val;
Enter fullscreen mode Exit fullscreen mode

setX isn’t a pure function because it sets the value of x which is outside the function. This is called a side effect.

A side effect is a state change that’s observed outside the called function other than its returned value. setX sets a state outside the function, so it commits a side effect.

Another example would be the following:

const getCurrentDatePlusMs = (milliseconds) => +new Date() + milliseconds;
Enter fullscreen mode Exit fullscreen mode

getCurrentDatePlusMs returns a value that depends on +new Date() which always changes, so we can check the value of it with tests easily. It’s also hard to predict the return value given the input since it always changes.

getCurrentDatePlusMs isn’t a pure function and it’s also a pain to maintain and test because of its ever-changing return value.

To make it pure, we should put the Date object outside as follows:

const getCurrentDatePlusMs = (date, milliseconds) => +date + milliseconds;
Enter fullscreen mode Exit fullscreen mode

That way, given the same date and milliseconds, we’ll always get the same results.

Immutability

Immutability means that a piece of data can’t be changed once it’s defined.

In JavaScript, primitive values are immutable, this includes numbers, booleans, strings, etc.

However, there’s no way to define an immutable object in JavaScript. This means that we have to be careful with them.

We have the const keyword, which should create a constant by judging from the name of it, but it doesn’t. We can’t reassign new values to it, and we can’t redefine a constant with the same name.

However, the object assigned to const is still mutable. We can change its properties’ values, and we can remove existing properties. Array values can be added or removed.

This means that we need some other way to make objects immutable.

Fortunately, JavaScript has the Object.freeze method to make objects immutable. It prevents property descriptors of an object from being modified. Also, it prevents properties from being deleted with the delete keyword.

Once an object is frozen, it can’t be undone. To make it mutable again, we have to copy the object to another variable.

It applies these changes to the top level of an object. So if our object has nesting, then it won’t be applied to the nested objects.

We can define a simple recursive function to do freeze level of an object:

const deepFreeze = (obj) => {
  for (let prop of Object.keys(obj)) {
    if (typeof obj[prop] === 'object') {
      Object.freeze(obj[prop]);
      deepFreeze(obj[prop]);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The function above goes through every level of an object and calls Object.freeze on it. This means it makes the whole nested object immutable.

We can also make a copy of an object before manipulating it. To do this, we can use the spread operator, which works with objects since ES2018.

For example, we can write the following function to make a deep copy of an object:

const deepCopy = (obj, copiedObj) => {
  if (!copiedObj) {
    copiedObj = {};
  }

  for (let prop of Object.keys(obj)) {
    copiedObj = {
      ...copiedObj,
      ...obj
    };
    if (typeof obj[prop] === 'object' && !copiedObj[prop]) {
      copiedObj = {
        ...copiedObj,
        ...obj[prop]
      };
      deepCopy(obj[prop], copiedObj);
    }
  }
  return copiedObj;
}
Enter fullscreen mode Exit fullscreen mode

In the code above, we create a new copiedObj object to hold the properties of the copied object if it doesn’t exist yet, which shouldn’t in the first call.

Then we loop through each property of the obj object and then apply the spread operator to copiedObj and obj to make a copy of the values at a given level. Then we do this recursively at every level until we went through every level, then we return the final copiedObj .

We only apply the spread operator if the property doesn’t exist at that level yet.

How do we know that this works? First, we can check if properties of each value are the same, which it is if we run console.log on it. Then we can also check with the === if the copied object has the same reference as the original object.

For example, if we have:

const obj = {
  foo: {
    bar: {
      bar: 1
    },
    a: 2
  }
};
Enter fullscreen mode Exit fullscreen mode

Then we can check by writing:

const result = deepCopy(obj);
console.log(result);
Enter fullscreen mode Exit fullscreen mode

To check the content of result .

We should get:

{
  "foo": {
    "bar": {
      "bar": 1
    },
    "a": 2
  }
}
Enter fullscreen mode Exit fullscreen mode

This is the same as obj . If we run:

console.log(result === obj);
Enter fullscreen mode Exit fullscreen mode

we get false , which means the 2 aren’t referencing the same object. This means that they’re a copy of each other.

Pure functions and immutability are important parts of functional programming. These concepts are popular for a reason. They make outputs predictable and make accidental state changes harder.

Pure functions are predictable since they return the same output for the same set of inputs.

Immutable objects are good because it prevents accidental changes. Primitive values in JavaScript are immutable, but objects are not. We can freeze it with the Object.freeze method to prevent changes to it and we can also make a copy of it recursively with the spread operator.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .