ReasonML is an alternative syntax for OCaml by Facebook, that has gained traction as a language for writing frontends alongside React. It has impressive type inference, blazing fast compile times, and a powerful module system.

As Reason is a syntax for OCaml, it also comes with support for object-oriented programming. I get the impression that object-oriented programming is typically not recommended in OCaml, and this seems to also apply to ReasonML. Hence there is little documentation for using objects in Reason ML -- there's just a single page in the official documentation.

As I struggled to find any resources documenting the object-oriented aspect of the language, I felt compelled to write something for others without an OCaml background.

Now, my goal in writing this isn’t to push object-oriented programming. Instead, it is to highlight some of the features that the object support provides. For me, I find the real benefit is structural subtyping (row polymorphism), which I have been able to utilise to write pure functional code using the RIO Monad.

An Introduction to Objects in Reason

ReasonML is typically demonstrated as a functional language alongside Reason-React. In such tutorials you will often see records, but never objects, which seem to be a little discussed feature of the language.

Unlike Records, Objects are able to mutate their own state. Whilst it is possible to have a mutable field or reference on a record, it is not possible for a record to contain a method that mutates that state (it would have to be a function declared elsewhere).

Let’s demonstrate how an object can mutate its own state using a simple type for a cat, with an immutable name and a mutable age. This example reminds me of the sort of code I to write 10 years ago in Java… there are getters for both fields, but a setter for the age. Note the . after the brace, which indicates that this is an object and not a record.

type cat = {
  getName: string,
  getAge: int,
  setAge: int => unit,
  describe: string
Unfortunately, Prism.js doesn't highlight objects very well!

Now lets see one way to implement that type:

let makeCat: (string, int) => cat = (name, age) => {
  as self; // this is a reference to the object itself
  val mutable catAge = age;
  pub getName = name;
  pub getAge = catAge;
  pub setAge = age => catAge = age;
  pri ageString = string_of_int(catAge);
  pub describe = self#getName ++ " is " ++ self#ageString ++ " years old."

There’s a few things to take from this.

as self; This is a reference to the object itself and you can call it whatever you wish (even _ if you don’t require it). You can think of this like self in Python or this in Java or TypeScript.

val mutable catAge = age Here we define an mutable instance variable to hold the cat’s age. We don’t need to do this for name as it’s immutable. This can only be accessed using the public getAge method.

pub getAge = catAge This is a method for fetching the age. As it is prefixed with pub the method is public. Note that it refers to the mutable reference and not the immutable age value we use to construct the instance.

pri ageString On the other hand, this is a private method which cannot be called outside of the object definition.

pub describe = self#getName ++ " " ++ self#ageString ++ "years old"; Note that unlike with values and variables, you must use the self reference to call the other methods on the object. The # syntax is a little strange compared to the dot notation used by records.

Finally, an object does not require a type definition. You can define an object with no type and use it within a function. If you so wish you can delay defining the object until you need it.

let double = x => x#value * 2;

let myObject = {
  as _;
  pub value = 3;

let result = double(myObject); // 6

Using Classes to Construct Objects

Whilst I used a function to build my object, you can use a class instead. The code is very similar:

class cat(name, age) = {
  as self;
  val mutable age = age;

  pub getName = name;
  pub getAge = age;
  pub setAge = (newAge: int) => age = newAge;
  pri ageString = string_of_int(age);
  pub describe = name ++ " is " ++ self#ageString ++ " years old."

Calling Methods on Objects

Once you’ve constructed an object you can call it’s methods using the # syntax. The simple example below demonstrates that we are mutating the state of the cat object:

let myOtherCat = new cat("Top Cat", 4);

myOtherCat#getAge // 6
myOtherCat.describe // "Top Cat is 6 years old."

Apart from the strange syntax, the basics of objects and classes are rather simple. Whilst you could use these techniques to do build object-oriented systems in ReasonML, it's not common to do so – nor is it common in OCaml. Modules are recommended for most use cases, whilst objects are only used when polymorphism and inheritance are required. This article has only scratched the surface of what is possible with objects and I will expand upon in this in later posts.

Next up, implementing inheritance and virtual types.