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 };
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);
};
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>};
};
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
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
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)
Awesome series! Really. Thanks.
I can't seem to log
myImmutableCat
ormyNewCat
. Is there a trick to that?Thank again.
I presume because objects don't print as nicely as records. As reason will try to print the methods alongside the values.
One way would be to add a
show
method to all your objects, where you manually make a string.