Raine Raine - 2 months ago 6
Javascript Question

How do I make a custom immutable type using facebook/immutable-js?

I am attempting to make a simple immutable Interval type. It has a start and an end property. Is there a straightforward way to utilize the immutable-js library to create your own custom immutable types?

My current attempt at using a containment pattern with Immutable.Map and Immutable.List is not working so well.

difference
, for example, should return an immutable list of Intervals, but because it creates new instances of Interval, the List it returns will not pass equality.

It feels like I am taking the wrong approach, but I'm not sure how. I could easily represent the data with
Immutable.Map
, but I want to include
difference
in its public interface, and not include general
Map
methods like get and set. i.e. I want it to be immutable, yet be its own type with its own interface.

var Immutable = require('immutable')

function Interval(start, end) {
this.value = Immutable.Map({
start: start,
end: end
})
}

Interval.prototype.start = function () {
return this.value.get('start')
}

Interval.prototype.end = function () {
return this.value.get('end')
}

Interval.prototype.equals = function(interval) {
return this.value.equals(interval.value)
}

Interval.prototype.difference = function (subtrahend) {
var nonoverlapping = subtrahend.start() > this.end() || subtrahend.end() < this.start()

return nonoverlapping ? Immutable.List.of(this) :
Immutable.List.of(
new Interval(this.start(), subtrahend.start()),
new Interval(subtrahend.end(), this.end())
)
.filter(function(interval) {
return interval.end() > interval.start()
})
}


Some tests:

it('should return an empty array for an exact difference', function() {
var interval = new Interval(80, 90)
interval.difference(interval)
.equals(Immutable.List())
.should.be.true
})

it('should return an array with the same Interval for a missed difference', function() {
var interval = new Interval(80, 90)
console.log(interval.difference(new Interval(50, 60)).toJS());
interval.difference(new Interval(50, 60))
.equals(Immutable.List.of([
{ value: Immutable.Map({ start: 80, end: 90 }) }
]))
.should.be.true
})

Answer

Why not use Record type:

var Interval = Immutable.Record({start: 0, end: 1})

This creates the record type for you, defining start and end properties with default values of 0 and 1, respectively.

Now to create instances of Interval, just do:

var newInterval = new Interval() //will have default values above
var newInterval = new Interval({start: 10, end: 30)

Note that instances of Record type are just value types, so they should just contain data. You can, of course, define functions that operate on this data value.

var difference = function (interval1, interval2) {
  var nonoverlapping = interval2.start > interval1.end || interval2.end < interval1.start

  return nonoverlapping ? Immutable.List.of(interval1) :
    Immutable.List([
      new Interval({start: interval1.start, end: interval2.start}),
      new Interval({start: interval2.end, end: interval1.end})
    ])
    .filter(function(interval) {
      return interval.end > interval.start
    });
}

Equality you get for free, using Immutable.is(interval1, interval2)

For more information on the record type: http://facebook.github.io/immutable-js/docs/#/Record

Comments