June 21, 2016

Immutable Data with Immutable.js

by Jscrambler

immutable-data-with-immutable.js

In this post we are going to learn some concepts about working with immutable data and immutable data structures using Immutable.js which provides us many highly efficient Immutable data structures.

The immutability refers to the way data behaves after being instanced, so that no mutations are allowed. In practice, mutations can be split in two groups: visible mutations and invisible mutations. Visible mutations are those that either modify the data or the data structure that contains it in a way that can be noted by outside observers through the API. Invisible mutations are changes that cannot be noted through the API. In a sense, invisible mutations can be considered side-effects.

There are some benefits when compilers and runtimes can be sure that data cannot change:

  • Persistence becomes easier;
  • Copying becomes constant, because you can’t change the data, you can only create a new reference from the existing instance of the original data by copying operation;
  • No locks are needed to synchronize data in multiple threads, because the data cannot change;

logo_immutable

The Immutable.js is a library created from Facebook to work with immutable collections in JavaScript, it provides many persistent immutable data structures like: List, Stack, Map, OrderedMap, Set, OrderedSet and Record.

Setting up the Immutable.js library

To install this library you can see more details in this link: facebook.github.io/immutable-js.

This library works on both Node.js and the browser. In this post we are going to use it on Node.js but all the examples will work exactly the same on the browser.

To start off let’s install immutable.js on our machine by running this command in the terminal:

npm install immutable  

And now, to test our first code, let’s create a simple immutable map:

var Immutable = require('immutable');  
var client = Immutable.Map({  
    name: 'John',
    age: 25
});
console.log(client.get('name')); // 'John'  
console.log(client.get('age')); // 25  

Try Jscrambler for Free!

After you create a Map, you can use the map.get(‘key’) to access their data by providing a key. If you need to update some data from this Map, you just use the map.set(‘key’) simple as that, but this function won’t change the current internal state of this Map because of the immutable behavior of this data structure, so instead change it internally, this function will return a copy of the current Map with some data changed. See the example below:

var Immutable = require('immutable');  
var client = Immutable.Map({ name: 'John', age: 25 });  
var newClient = client.set('name', 'Mary');  
console.log(newClient.get('name')); // 'Mary'  
console.log(newClient.get('age')); // 25  
console.log(client.get('name')); // 'John'  
console.log(client.get('age')); // 25  

Sometimes, tracking mutations and maintaining state can be very difficult to handle. Working with immutable data encourages you to think differently about how data flows through your application.

Immutable collections should be treated as values rather than objects. While objects represent something which could change over time, a value represents the state of that thing at a particular instance of time. This principle is most important to understand the appropriate use of immutable data.

Exploring the main data structures

  • List: is a immutable representation of an array. This List has the main functions of a JavaScript array:
var Immutable = require('immutable');  
var scores1 = Immutable.List([2, 4, 6, 8]);  
console.log(scores1.size); // 4  
var scores2 = scores1.push(10); // [2,4,6,8,10]  
var scores3 = scores2.pop().pop(); // [2,4,6]  
var scores4 = scores3.shift(); // [4,6]  
var scores5 = scores4.concat(10, 12, 14); // [4,6,10,12,14]  
  • Stack: this is the classic FILO (first in, last out) data structure.  To modify the stack you can only use the push() and pop() methods and to access their elements you can use the get(index) method, take a look:
var Immutable = require('immutable');  
var stack = Immutable.Stack();  
var scores = stack.push(10, 12, 14);  
console.log(scores.size); // 3  
console.log(scores.get()); // 10  
console.log(scores.get(0)); // 10  
console.log(scores.get(1)); // 12  
console.log(scores.get(2)); // 14  
var newScores = scores.pop(); // [10, 12]  
  • Map: is a key-value data structure, which represents a JavaScript object, but it’s an immutable one. In the constructor you add the key and values. To access some value, you use the map.get('key') and to change some value you use the map.set('key', newValue), but this change will generate a new Map instead of mutating the current one. We have already seen the Map in action in the beginning of this post, so let’s jump to the next one.
  • OrderedMap: this is a mix of objects and arrays, in fact, this data structure can be treated as an object by using the orderedMap.get('key') and orderedMap.set('key', newValue) functions, and there are some array methods too, like orderedMap.first() and orderedMap.last() methods. The keys are ordered based on the order in which they were added to the map. And you can re-define the order of these keys by using the orderedMap.sort() and orderedMap.sortBy() methods which will return a new ordered map.
var Immutable = require('immutable');  
var clients = Immutable.OrderedMap()  
    .set('John', 25)
    .set('Mary', 27);
console.log(clients.first(), clients.last()); // 25, 27  
console.log(JSON.stringify(clients)); // '{"John": 25, "Mary": 27}'  
var olderClients = clients.sortBy(function(value, key) {  
    return -value;
});
console.log(JSON.stringify(olderClients)); // '{"Mary": 27, "John": 25}'  
  • Set: is an immutable array of unique elements. No duplicated values are allowed, so if you add a duplicated value, the second one will be ignored.
var Immutable = require('immutable');  
var set1 = Immutable.Set([1, 2, 3, 3]);  
var set2 = Immutable.Set([4, 5, 5]);

console.log(set1.count()); // 3  
console.log(set1.toArray()); // [1,2,3]  
console.log(set2.count()); // 2  
console.log(set2.toArray()); // [4,5]

var union = set1.union(set2);  
console.log(union.count()); // 5  
console.log(union.toArray()); // [1,2,3,4,5]  
  • OrderedSet: this is a Set with keys ordered according to the time of addition, similar to OrderedMap, but it’s a Set.
var Immutable = require('immutable');  
var orderedSet1 = Immutable.OrderedSet([1, 2, 2]);  
var orderedSet2 = Immutable.OrderedSet([2, 1, 2]);

console.log(orderedSet1.count()); // 2  
console.log(orderedSet1.toArray()); // [1,2]  
console.log(orderedSet2.count()); // 2  
console.log(orderedSet2.toArray()); // [2,1]

var intersected = orderedSet1.intersect(orderedSet2);  
console.log(intersected.count()); // 2  
console.log(intersected.toArray()); // [1,2]  
  • Record: is a JavaScript class on which you can set default values. In absence of a value, the default value will be used. This is useful to instantiate immutable objects.
var Immutable = require('immutable');  
var Client = Immutable.Record({  
    name: 'John',
    age: 25
});
var john = new Client();  
console.log(john.toJSON()); // Object { name: 'John', age: 25 }  
var mary = new Client({  
    name: 'Mary',
    age: 20
});
console.log(mary.toJSON()); // Object { name: 'Mary', age: 20 }  

Learning some useful functions

The immutable.js library has some useful functions that facilitate the manipulation of immutable data structures. Here are some useful modules you must learn how to use:

  • Immutable.Seq(): represents a sequence of values, but may not be backed by a concrete data structure. It allows you to run a chain of operations, see this example:
var object = Immutable.Seq({  
        a: 1,
        b: 1,
        c: 1
    })
    .flip()
    .map(function(key) {
        return key.toUpperCase()
    })
    .flip()
    .toObject();
console.log(object); // Map { A: 1, B: 1, C: 1 }  
  • Immutable.Range(): returns a sequence of numbers from start (inclusive) to end (exclusive), by step values. The default values for theses variables are: start=0, end=infinity and step=1. When start and end are equal values, it returns an empty range.
console.log(Immutable.Range()); // [0,1,2,3...]  
console.log(Immutable.Range(5)); // [5,6,7,8...]  
console.log(Immutable.Range(5, 10)); // [5,6,7,8,9]  
console.log(Immutable.Range(5, 10, 2)); // [5,7,9]  
console.log(Immutable.Range(5, 5)); // []  
  • Immutable.Repeat(value, times): returns a sequence of value repeated by X times. When times is not defined, returns an infinite sequence.
console.log(Immutable.Repeat('john')); // ['john', 'john', 'john'...]  
console.log(Immutable.Repeat('mary', 2)); // ['mary', 'mary']  

Conclusion

This is an awesome library to handle with immutable data structures. It corrects the flaws of underscore.js and lodash libraries, namely that operations of different data structures were forced on JavaScript arrays and objects, mixing the concept of data types, and losing immutability.

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.