Avoid side effects with immutable data structures

Guarantee fewer side effects by using immutable objects instead of JavaScript arrays.

Source code that is simple to read and understand is less likely to have hidden bugs. A great advice given in the blog post avoid forEach is to use specific semantic array iterators like map, filter, some instead of generic forEach. For example, when we want to construct new array with every item passed through a function double, a preferred way is to use map rather than forEach

1
2
3
4
5
function double(x) { return x*2; }
var a = [1, 2, 3];
var b = a.map(double);
// a is unchanged [1, 2, 3]
// b is [2, 4, 6]

Compare this to the same result using forEach, which requires extra variable.

1
2
3
4
var c = [];
a.forEach(function transform(x) {
c.push(double(x));
});

The semantic meaning of transform can only be inferred from reading its source code and then looking at the initialization of c above.

Unfortunately, this is not the end of the story. Array.prototype.map can still have side effects, even in simplest cases. For example, the callback function double can modify the original array!

1
2
3
4
5
6
7
8
function double(x, k, array) {
array[k] = 0;
return x * 2;
}
var a = [1, 2, 3];
var b = a.map(double);
console.log('a', a); // a [ 0, 0, 0 ]
console.log('b', b); // b [ 2, 4, 6 ]

How do we guarantee that map does not modify the original array? The simplest solution is to use immutable data structures. For example, seamless-immutable wraps native JavaScript data structures and guarantees the original data structure remains unchanged.

1
2
3
4
5
6
// same function double from above
var Immutable = require('seamless-immutable');
var c = Immutable([1, 2, 3]);
var d = c.map(double);
console.log('c after map', c); // [ 1,2,3 ]
console.log('d', d); // [ 2, 4, 6 ]

Notice that despite using the same callback function, the original Immutable object c remains unchanged after c.map call. We can observe this better by printing the array passed into double

1
2
3
4
5
6
7
8
9
10
11
12
function double(x, k, array) {
console.log('index', k, 'array', array);
array[k] = 0;
return x * 2;
}
var Immutable = require('seamless-immutable');
var c = Immutable([1, 2, 3]);
var d = c.map(double);
// prints
index 0 array [ 1, 2, 3 ]
index 1 array [ 1, 2, 3 ]
index 2 array [ 1, 2, 3 ]

Despite changing the passed data structure using array[k] = 0; statement, each iteration receives the unchanged array [1, 2, 3]. The updates are silently ignored. If we execute the same code in strict mode, trying to change the immutable data structure will generate a TypeError

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'use strict';
function double(x, k, array) {
array[k] = 0;
return x * 2;
}
var Immutable = require('seamless-immutable');
var c = Immutable([1, 2, 3]);
var d = c.map(double);
// prints
/src/index.js:3
array[k] = 0;
TypeError: Cannot assign to read only property '0' of [object Array]
at double (/immutable/seamless-index.js:3:12)
at Array.map (native)

When using immutable data structures instead of JavaScript native objects, our code and data flow becomes easier to understand and reason. map, some, filter, etc methods always return a new object, leaving the original array unchanged.

There are several immutable data libraries for JavaScript. I like seamless-immutable for its API compatible with JavaScript built-in objects and arrays. Other libraries, like immutable provide larger API with more methods, but might require more changed to use them.