TC39 proposal: Record and Tuples, the real immutable data structures in JavaScript.

Alberto de Murga
3 min readApr 26, 2021

--

const record = #{ prop: 1}; const tuple = #[1, 2, 3]
Records and tuples

One of the misleading use cases for beginners in JavaScript is to create constant arrays and objects. With the introductions of ES6, we got two new declarators: let, for mutable variables, and const, for constants. Many beginners believe that this will make their objects and array immutable, to discover later that they are not. The object or array itself is immutable, but not their content. So far, we have relied on the method Object.freeze to deal with this use case.

// This is supposed to be immutable, isn't it?
const obj = { a: 1 }
obj.a = 2
console.assert(obj.a === 1, 'wtf')
// Assertion failed: wtf

Introducing Record and Tuples.

Records are immutable arrays. Tuples are immutable objects. They are compatible with Object and Array methods. Essentially, you can drop a Tuple or a Record in any method that takes an object, or an array and it will behave as expected, unless this implies to modify the element. This applies to method of the standard library, iterators, etc.

// Record
const record = #{ x: 1, y: 2 }
// Tuple
const tuple = #[1, 2, 3, 4]
// We can use most of the methods that work with Arrays and Objects.
console.assert(tuple.includes(1) === true, 'OK, it will not print')
// Although they will return tuples and records.
console.assert(Object.keys(record) === #['x', 'y'], 'OK, it will not print')
// Iterators work too
for (const element of tuple) {
console.log(element)
}
// 1
// 2
// 3
// 4
// And you can nest them!
const nested = #{
a: 1,
b: 2,
c: #[1, 2, 3]
}
// Nope
tuple.map(x => doSomething(x));
// TypeError: Callback to Tuple.prototype.map may only return primitives, Records or Tuples

// This is ok
Array.from(tuple).map(x => doSomething(x))

However, with great power comes great responsibility.

The power

  • Comparison by value: Like other simple primitive types, they are compared by value, not by identity. Objects and arrays are equal if they are the same entity. Tuples and records are equal if they contain the same elements.
const objA = {a: 1}
const objB = {a: 1}
const objC = objA
console.assert(objA === objB, 'Same content, but different entities, false')
console.assert(objA === objC, 'They are the say, it will not print')
const recordA = #{a: 1}
const recordB = #{a: 1}
const recordC = recordA
console.assert(recordA === recordB, 'OK, will not print')
console.assert(recordA === recordC, 'OK, will not print')
  • You can convert to objects and array and the other way around: Using the functions Record() and Tuple.from().
const obj = { ...#{a: 1, b: 2}}
const record = Record({a:1, b:2})
const arr = [ ...#[1, 2, 3]]
const tuple = Tuple.from([1, 2, 3])
  • They are identified as distinct types: Using the operator typeof returns unique names for each of them.
console.assert(typeof #{a: 1} === 'record', 'this will not print')
console.assert(typeof #[1, 2] === 'tuple', 'this will not print')

The responsibility

  • They can only contain primitive types: They can only contain String, Number, Boolean, Symbol, BigInt, undefined, null, Record and Tuple. This is, no functions, objects, arrays, classes, etc.
  • You can use them in Maps and Sets, but not with WeakMaps and WeakSets: Quoting from the spec

It is possible to use a Record or Tuple as a key in a Map, and as a value in a Set. When using a Record or Tuple here, they are compared by value.

It is not possible to use a Record or Tuple as a key in a WeakMap or as a value in a WeakSet, because Records and Tuples are not Objects, and their lifetime is not observable.

  • JSON.stringify will work as expected, but JSON.parsewill still return objects and arrays: There is a proposal to add JSON.parseImmutableewhich will behave like JSON.parse but returning records and tuples instead of arrays and objects.

Conclusion

This addition is welcomed as it has been a struggle to define immutable values in JavaScript, and confusing for many beginners. Prior solutions implied using external libraries like Immutable.js ,workarounds in the standard library like Object.freeze or conventions to achieve similar results.

The proposal is currently on stage 2, so it is subject to changes. However, it already looks solid and I personally hope it makes it through and becomes a standard.

References

--

--

Alberto de Murga

Software engineer at @bookingcom. I like to make things, and write about what I learn. I am interested in Linux, git, JavaScript and Go, in no particular order.