DEV Community

RawToast
RawToast

Posted on • Edited on • Originally published at itazura.io

Immutable Updates of Objects in Reason

I've recently been playing around with objects in ReasonML and reading about using them in OCaml to get a better understanding, as there is a lack of good documentation on their use in Reason. So I thought I'd try and writing something about one of the interesting and lesser-known features of objects in ReasonML.

I am sure none of this will come as a surprise to a seasoned OCaml developer, but like many developers, I have no experience with Ocaml and keep finding out new features that can also used in our projects.

In ReasonML the spread operator is a great piece of syntactic sugar that makes immutable updates quick to implement, similar to the copy method of case classes in Scala. I was happy to find out that this was also possible with objects, allowing the creation of immutable, but updatable, objects. In OCaml, these are known as functional objects.

Most ReasonML developers have come across the syntax below to update immutable records:

type cat = {name: string, age: int};

let kitten = { name: "Charles", age: 1 };

let oldCat = { ...kitten, age: 4 };
Enter fullscreen mode Exit fullscreen mode

A similar technique exists for objects; however, it's not so well known amongst Reason developers, as I believe it is not included in the documentation. So at first, you might attempt something like the example below, which works but quickly becomes verbose as your records grow in size:

class immutableDog(name, age) = {
  as _;
  pub getAge: int = age;
  pub getName: string = name;
  pub setName: string => immutableDog = newName => new immutableDog(newName, age);
  pub setAge: int => immutableDog = newAge => new immutableDog(name, newAge);
};
Enter fullscreen mode Exit fullscreen mode

Instead, you can override values to make a new instance with only the specified updates, similar to records. The syntax is not so obvious:

class immutableCat(name, age) = {
  as _;
  val name = name;
  val age = age;
  pub getAge: int = age;
  pub getName: string = name;
  pub setName = newName => {<name: newName>}; // Creates a new instance.
  pub setAge = newAge => {<age: newAge>};
};
Enter fullscreen mode Exit fullscreen mode

I believe this syntax is known as the override construct {< ... >}, which returns a copy of the current object and allows you to override the value of instance variables or methods.

Let's see what it looks like in action:

let myImmutableCat: immutableCat = new immutableCat("Cool Cat", 5);
let myNewCat: immutableCat = myImmutableCat#setName("Dave"); // does not change myImmutableCat
Enter fullscreen mode Exit fullscreen mode

It is also possible to update values in the same way:

class immutable = (number) => {
  as _;
  val counter = number * number;
  pub makeNegative = {<counter: -counter>};
  pub getCounter = counter;
};

let myImmutable = new immutable(4);
let negative = myImmutable#makeNegative;
myImmutable#getCounter // 4
negative#getCounter // -16
Enter fullscreen mode Exit fullscreen mode

Thanks for reading, I hope you found this post useful and legible!

Based on a post in a series on using objects in Reason at itazura.io

Top comments (2)

Collapse
 
idkjs profile image
Alain

Awesome series! Really. Thanks.

I can't seem to log myImmutableCat or myNewCat. Is there a trick to that?

Thank again.

Collapse
 
rawtoast profile image
RawToast

I presume because objects don't print as nicely as records. As reason will try to print the methods alongside the values.

Js.log2("Does this work", myNewCat);

"Does this work" [[4,15,null,-278822339,null,243512077,null,461513217,null,588852681],16,"Cool Cat",5,"Dave",5]

One way would be to add a show method to all your objects, where you manually make a string.

class immutableCat(name, age) = {
  as _;
  val name = name;
  val age = age;
  pub show = "Cat {name: " ++ name ++ ", age: " ++ string_of_int(age) ++ "}";
};

let myImmutableCat: immutableCat = new immutableCat("Cool Cat", 5);

let show = obj => obj#show;  // put this in a Utility module somewhere
Js.log2("Does this work", show(myImmutableCat));