Practical Functional JavaScript — Step 1: Immutability and Pure Functions

Kumar Bhot
webf
Published in
6 min readFeb 5, 2018

--

This is the 1st article of the series Practical Functional JavaScript. [Next].

Imagine a simple system with two components that need to connect to an external entity at times.

To facilitate the external communication, the system generates a connection object and shares it with both the components. In a typical Web User Interface Application implemented with Object-oriented Programming, these two components are most likely some UI/View Components, instances of say, UIComponent class and the connection object, too, might be created out of say, Connection class. Since the connection object is shared among the two components, what would happen if one component accidentally end up changing it? It might still be fine if the component at fault pays for its mistake by malfunctioning; but the innocent component’s failure would be unacceptable. For this reason, the system supervisor will issue a warning that the connection object be never modified; at least at its original reference.

The problem is, this warning or guideline is issued for humans. And humans do err! That’s a default setting for our race.

Computer programs must not have to rely on human’s capability to stay focused and not make mistakes. The result of such a setup is ‘bugs’. And we see a lot of them, everywhere.

In the OOP world, a typical thinking for protecting the critical properties of an object (state) is Encapsulation. In our heads, when we design these objects (Abstractions), we also have to think about their protection (Encapsulation) — creating a diversion from domain-driven design to a design that reveals the implementation specifics. This very mixing of Abstraction with Encapsulation is the biggest source of all evils. Why? Imagine such a system with hundreds or even dozens of objects and you have to visualize all of those objects at once.

Haskell is a beautiful counterexample. For those who aren’t familiar, there is no concept of Encapsulation in Haskell as it doesn’t allow any state to be modified once it is declared. The state of a system and its data models must remain separated. For this purpose, mutation of data must be avoided at any cost.

The quest for simplicity

Imagine a system where no data is ever allowed to change. A part of the code can never modify an object that the other part of the code has. In case of our original example of two components and the shared connection object, the two components receive the same shared but frozen connection object which cannot be modified at all. If a component tries to modify it, a new object is generated. Wouldn’t that be simpler and maintainable?

Still need more reasons to avoid mutation? Consider these:

  • Defensive copying — as mentioned in the initial example, we worry about protecting the object
  • Change detection — one of the major reasons why AngularJS had to be dropped and rewritten as Angular
  • Memoization — application of advanced memoization techniques is a huge challenge with mutable objects
  • Overall state maintenance — when an object has multiple source of editing, the control on its safety is lost

This concept of not allowing the state to be modified after initialization is called as Immutability.

Immutability

Let’s agree, mutability is the root cause to most of the run-time exceptions.

At the core of the Functional Programming Practices is Immutability — instead of modifying a state, we create a new state. In other words, the state is transformed as it is passed through sequence of functions.

Example problem statement — where each of the step must be executed separately and in the exact given order:

  • Take two numbers as inputs
  • Square the first number
  • Double the second number
  • Add the two numbers
  • Subtract double of the first number from the answer
  • Multiply the answer by 5, if answer is even, by 10 if answer is odd
  • Add 101 to the answer
  • Return 1, if answer is a Prime number otherwise return the answer as is

Here is an example of normal (imperative) approach:

The very first problem, as you can see, is readability — inexpressive.

There are at least 4 points where the value of the variable answer (state) can possibly be changed (mutated). Also, inside the isPrime() method, i has a similar fate. This is not necessarily the ‘best example’ of ‘bad mutation’, but for the sake of this discussion, let’s assume that “every” form of mutation in a program is bad.

Normally, the situations where we need mutations are numerous. One of the simplest and most common is the flag that toggles the waiting throbber to indicate progress of server communication.

Avoid state mutation (Source: http://www.rahulpnath.com/blog/avoid-state-mutation/)

The solution to avoid mutation might differ from situation to situation. It would take combination of multiple disciplines from the Functional Programming approach — breaking down into smaller units (functions), recursion, function composition, destructuring and so on.

Pure Functions

The Why, What and How of Pure Functions are beautifully explained for us at many Functional Programming references. Here are a few good pointers:

The aim here is to get you started with Pure Functions in your daily JavaScript coding.

Before we start converting our existing functions and moving code to Pure Functions, let’s take a quick re-look at the understanding of what a Pure Function means:

It does:

- Perform operation only on the given input
- Deliver output only through the ‘return’ statement

It does *not*:

- Create side effects, whatsoever e.g. modifying DOM, making a server call, or modifying anything outside its local scope

Let’s start applying the concepts of Immutability and Pure Functions.

  1. Once again, “say no to mutation”
  2. Break down your code into very small but meaningful functions
    — Usually a line or two is a sufficient size
  3. Keep your functions pure
    — If a function needs something, make it available through its arguments
    — Avoid closures for maintaining the application state
    — Single argument is ideal; try hard — a guideline and not a rule
  4. Let a function do only one thing and name the function well. While calling a function from within another, it must be within the intended responsibility of the caller function.
  5. Whenever #3 is not possible, separate out each impurity

Let’s see, how we can achieve the solution to our problem with a Functional approach — especially combining the above two concepts — Immutability and Pure Functions.

What we have done here is passed the state through a series (pipe) of sequential functions to achieve the goal.

You can clearly see the benefits of the functional approach here — readability (because everything is so declarative), re-usability, accuracy (confidence) and above everything else, testability (more on this later).

[Just to provide a balanced view, in this particular solution we are extending the prototype of a native JavaScript type which isn’t a good idea. Achieving the same with cleaner/pure-functional approach would need a lot of boilerplate code or a sophisticated Functional JavaScript library. Secondly, one can argue about possible negative impact to the program performance due to the use of recursion and number of function calls. That can be optimized further but the case here is of ‘state immutability’ and ‘function purity’.]

An add-on note

When you’re dealing with passing Arrays and Objects around, you know that in JavaScript, they are always copied by reference; so, try destructuring.

Also, practice safer Array methods like slice(), concat(), map()instead of splice() and push().

Takeaway

  1. Stop using var and let altogether; explore Immutable, Immer.js, Seamless-Immutable and Ramda
  2. Keep your functions pure; single responsibility, as smaller as possible and separate impurities
  3. Be explicit about mutations, when you must use them; use closures but avoid using them for maintaining an implicit state

Consider this as Step 1 towards Practical Functional JavaScript. Practice this for a few days before moving on to the next step.

--

--