Creating friendly frozen object APIs using ECMAScript 6 Proxies

There is a lot of talk nowadays related to the benefits of immutability in programming languages. Functional languages such as F# and Scala are gaining more popularity because of this. Commonly considered object oriented languages such as C# and Java go more functional with each release, Java including lambdas in version 8 (although that is not directly related to immutability) and C# having the immutable collections and new features in version 6 such as get only auto-properties, initializers for automatically implemented properties and primary constructors, which can be combined together to write immutable classes/structures with less code.

Usually in those cases, the most highlighted benefit of immutability is that since you don’t have to worry about mutable state you can write lock free multithreaded code, a thing that is becoming more important as the processing power of one CPU core is not going to continue growing in the future (unless until Quantum computers arrive, but I have no idea of how that would work). That benefit comes from the guarantees that computers can make about immutable state.

Immutability in ECMAScript 5

ECMAScript 5 introduced the ability to make objects immutable through the use of Object.freeze(obj). As the documentation explains, the method:

  • Prevents the addition of new properties to the object.
  • Prevents the removal of existing properties
  • Prevents changes on any of the characteristics of existing properties.

“In essence the object is made effectively immutable.”

Considering the current state of JavaScript writing multithreaded is not one of the goals for which this feature was designed. Nevertheless, considering JavaScript’s dynamic nature I think it was done because:

  • It should be potentially possible for engines to optimize code based on the knowledge that an object is immutable (although it seems that is not the case right now).
  • Sometimes it’s easier to reason about things if you are sure they won’t change.

APIs for working with immutable elements

Considering all of the above, a fairly common scenario would be to want to create a new object similar to an existing one but changing some values from the original object. F# provides the following syntax for records through the usage of with:

type Point = {
  X: int;
  Y: int;
}

let point = { X = 0; Y = 0; }
let point2 = { point with X = 100; }

A similar API in C# would look like this (and some pains about it are documented in this post):

public class Point {
    private readonly int x;
    private readonly int y;

    public Point(int x, int y){
        this.x = x;
        this.y = y;
    }

    public Point WithX(int x){
        return new Point(x, this.y);
    }

    public Point WithY(int Y){
        return new Point(this.x, y);
    }

    public int X { get { return this.x; } }
    public int Y { get { return this.y; } }
}

var p = new Point(0, 0);
var p2 = p.WithX(20);

To achieve the same thing in JavaScript we could create methods for each field, similar to the C# example. That could be either manual or automatic, the latter by calling a function passing the object as a parameter and creating a function for each field.

And then there’s another option…

Using Proxy to implement the API

The first thing we need to do is agree on how the API will be consumed. My proposal is:

function Vehicle(hasEngine, hasWheels) {
    this.hasEngine = hasEngine || false;
    this.hasWheels = hasWheels || false;
};

function Car (make, model, hp) {
    this.hp = hp;
    this.make = make;
    this.model = model;
};

Car.prototype = new Vehicle(true, true);

var car = immutable(new Car('Hyundai', 'Elantra', 10));
var car2 = car
  .withHp(car.hp + 20) // creating with an existing property and a new value
  .withHasMirrors(true); // creating with a new property;

console.log(car2.hp); // prints 30
console.log(car2.hasMirrors); // prints true

Now let’s set some requirements:

  • When calling immutable(obj) the object obj becomes immutable.
  • The with... methods only perform a shallow copy. They should maintain the prototype chain.
  • Only fields can be set using with..., methods can’t.

Before we get into the implementation details it’s important to remark that the Proxy feature is part of the unfinished ECMAScript 6 specification. The documentation is really good so I won’t delve into the details of how the feature works.

A potential implementation could look like this:

var immutable = (function(){
  function isFunction(f) {
    var getType = {};
    return f && getType.toString.call(f) === '[object Function]';
  }

  function shallowCopy(object){
    var copy = Object.create(Object.getPrototypeOf(object));

    Object.getOwnPropertyNames(object).forEach(function (name){
      copy[name] = object[name];
    });

    return copy;
  }

  return function immutable(object){
    var proxy = new Proxy(object, {
      get: function(target, originalName){
        if (originalName.indexOf('with') === 0 && originalName.length > 4)
        {
          var capitalizedName = originalName.substring(4);
          var name = capitalizedName.charAt(0).toLowerCase() + capitalizedName.slice(1);
          var attribute = target[name];
          if (!attribute || !isFunction(attribute)){
            return function(value){
              var copy = shallowCopy(object);
              copy[name] = value;
              return immutable(copy);
            };
          }
        }

        return target[originalName];
      }
    });

    Object.freeze(object);

    return proxy;
  };
})();

Testing things out

At the time of writing the only browser that supports the latest version of the specification is Firefox. I’m using version 29.0.1 and running this gist.

The output is:

10 sample.js:59
"Hyundai" sample.js:60
"Elantra" sample.js:61
true sample.js:62
true sample.js:63
undefined sample.js:64
30 sample.js:68
"Hyundai" sample.js:69
"Elantra" sample.js:70
true sample.js:71
true sample.js:72
true sample.js:73