Introduction to Immutable.js


Most developers emphasize immutability when dealing with functional programming. Code written in functional style is testable, because the functions operate on data treated as immutable. In practice though, I see this principle violated from time to time. I will present one way to force yourself to eliminate side effects in your code: using immutable.js.

Immutable.js to the rescue

You can use Immutable.js in your code by installing it as an npm module or loading the source file immutable.min.js.

Let’s explore an immutable map as our first example. A map is basically an object consisting of key-value pairs.

First, a person is created with the name, birth and phone attributes. The changePhone function returns a new immutable map. When the changePhone function is executed, person2 is created as a return value, and person2 is strictly different than person. The phone numbers of each person map can be accessed via the get method. The properties of the maps are hidden behind the get/set interface, therefore they cannot be directly accessed or modified.

The immutable abstraction is intelligent enough to detect when an attribute is changed to the same value as before. In this case, both == and === comparisons return true, as the return of the o.set method is o. In all other cases, a real change takes place, and a new object reference is returned. This is why person5 is not equal to person even though they have the exact same keys and values. Mind you, in many real-life scenarios, person is supposed to be a thrown-away value after a modification takes place, therefore a comparison between person and person5 is rarely useful.

If we wanted to check the equality of attribute key-value pairs of person and person5, we can use the equals method of the immutable map interface:

Immutable data structures are amazing, but we don’t always need them. For instance, we normally send JSON payloads to the server instead of an immutable.js data structure. Therefore, there is a need to convert the immutable.js data structure into a JavaScript object or a JSON string.

Both the toObject and the toJSON methods return a JavaScript object representation of the immutable map. As a consequence of the return value of toJSON, JSON.stringify can directly be used on immutable data structures to create a JSON string for serialization.

Assuming proper usage of immutable data structures, maintainability of our application is expected to improve. Using immutable data structures indeed results in side-effect free code.

Immutable.js data structures

Immutable.js has the following data structures:

  • List,
  • Stack,
  • Map,
  • OrderedMap,
  • Set,
  • OrderedSet,
  • Record,
  • lazy Seq.

Let’s briefly explore all of these data structures.

List: a List is an immutable representation of a JavaScript array. The usual array operations are available with the twist that their return value is a new immutable object whenever the content of the original object is changed.

Stack: first in, last out data structure, defined with the usual operations. The serialized equivalent of a stack is an array, where the element with index 0 corresponds to the element to be popped. All elements of the stack can be accessed without popping via the get method. However, our only options for modifying the stack are to push and pop.

Map: we have already seen the Map data structure in action. It is the immutable.js representation of a JavaScript object.

OrderedMap: an ordered map is a mixture of objects and arrays. It can be treated as an object with the feature that its keys are ordered based on the order in which they were added to the map. Modifying the value belonging to an already added key does not result in a change of the order of keys.

The order of the keys can be re-defined using the sort or sortBy methods, returning a new immutable ordered map.

The dangerous part about using an ordered map is that its serialized form is a simple object. Given that some languages such as PHP also treat their objects as ordered maps, in theory, communicating via order maps could work. In practice, I don’t recommend this form of communication for the sake of clarity.

Set: A Set contains an array of unique elements. All usual operations are available. In theory, the order of elements in the set should not matter.

OrderedSet: An OrderedSet is a Set with elements ordered according to the time of addition. When the order of elements matter, use an ordered set.

Record: a record is like a JavaScript class with default values for some keys. When instantiating a record, the values for the keys defined in the record can be given during instantiation. In absence of a value, the default value of the record is used.

Seq: sequences are lazy finite or infinite data structures. Elements of a Seq are only evaluated on demand. Depending on the type of sequence, we can talk about a KeyedSeq, an IndexedSeq or a SetSeq. Finite and infinite sequences can be defined using

  • Immutable.Range(),
  • Immutable.Repeat(),
  • a mutation of a seq using a functional utility such as map, filter.

Finite Seqs can also be defined using enumeration.

One of the benefits of lazy evaluation is that infinite sequences can be defined. Another one is performance. As a riddle, try to determine the console messages emitted by the following code:

The results may be surprising. Given that evaluation is lazy and we are dealing with a finite data structure, elements of the seasons Seq can be accessed directly. Therefore, the upper case version of each element is calculated on demand. When the toJS method of seasons is called, all four elements are calculated from scratch. By default, there is no memorization in lazy sequences. Therefore the result is:

The above observations hold for infinite data structures as well. Elements are only calculated on demand, without memorization.

Summary

Immutable.js is a nice library providing immutable data structures. It corrects the flaws of underscore.js, namely that operations of different data structures were forced on JavaScript arrays and objects, mixing the concept of data types, and losing immutability. Even though lodash.js attempted to correct some of these flaws, the compatibility with underscore.js still results in a counter-intuitive structure. Although lazy.js came with lazy loading, it was especially known for being a lazy version of underscore.

The name immutable.js very well reflects that we need to deal with immutable data structures as a necessary condition for exercising pure functional programming. The emphasis on using correct data structures results in increased maintainability of your code.

  • Great intro, what’s the best way to initialize from JSON representation (for example from HTTP api call) to immutable?

    • FennNaten

      Hi,
      Immutable offers a fromJS() method to directly turn a js object to an immutable structure (deep copy for nested structures). By default js objects are turned to immutable maps and arrays to immutable lists. If you now upfront the structure of the data and want some of the collections to be turned to more specialized collections, fromJS() also takes a ‘reviver’ function as an optional second parameter, which is given the current key name and the current collection as a sequence, and from where you can return what you want.

  • Map seems rather clunky. What’s the rationale for get and set? Why not use Object.defineProperty() to create a prop with writable: false?

    • Lars Jeppesen

      Agree, it’s annoying to have to use myMap.toObject() every time I want to use it in Javascript

    • Patrik Kompuš

      generally getters and setters: encapsulation

      what if you want to make it writable under some conditions?
      what if you want to make a call everytime the property changes? etc.

  • kun luo

    Good intro!