Objects with Records, in OCaml

With the rescript compiler version 8.4.2, the ocaml objects are not available.

Here is a tutorial about how to simulate OO with records.

In Python, the objects are available in the methods as the first parameter usually called "self", so we will re-use this convention to contain the generic data of our objects as a field called .self in our generic objects implemented with a record.

So here in our type "o", we have the field .self to store the generic data, one method called .meth (but you can put several), and a second method called .new_o to replicate functional update methods of ocaml objects.

type 'a o = {
  self : 'a;
  meth : 'a -> int -> int;
  new_o : 'a -> int -> 'a;
}

Here there is a generic type parameter 'a, if several objects have a different type for the .self field it will not be possible anymore to store several of these objects in a single structure, like a list for example.

The solution is then to hide the self part by partial application.

Our generic record will then look like this, with no generic type 'a anymore:

(* our generic object *)
type g = {
  gmeth : int -> int;
  new_g : int -> g;
}

The original type o has a method .new_o which returns the new self element, which will be used to create the functional update method .new_g in the generic functional object.

type 'a o = {
  self : 'a;
  meth : 'a -> int -> int;
  new_o : 'a -> int -> 'a;
}

(* type obj-1 *)
let meth1 = (fun a b -> a + b)
let new_o1 = (fun a c -> let self = (a + c) in (self) )
let o1 = {
  self = 5;
  meth = meth1;
  new_o = new_o1;
}

(* type obj-2 *)
let meth2 = (fun (a1, a2) b -> a1 + a2 + b)
let new_o2 = (fun (a1, a2) c -> let self = (a1 + c, a2 + c) in (self) )
let o2 = {
  self = (3, 4);
  meth = meth2;
  new_o = new_o2;
}

(* wrap both in obj-g (generic) *)
type g = {
  gmeth : int -> int;
  new_g : int -> g;
}

(* hide self by partial application: *)

let rec new_g1 self =
  (fun v ->
    let self = o1.new_o self v in
    {
      gmeth = o1.meth self;
      new_g = new_g1 self;
    }
  )

let g1 = {
  gmeth = o1.meth o1.self;
  new_g = new_g1 o1.self;
}

let rec new_g2 self =
  (fun v ->
    let self = o2.new_o self v in
    {
      gmeth = o2.meth self;
      new_g = new_g2 self;
    }
  )

let g2 = {
  gmeth = o2.meth o2.self;
  new_g = new_g2 o2.self;
}

(* both objs can now be grouped in a single structure: *)
let gs = [ g1; g2; ] ;;

let gs = List.map (fun g -> g.new_g 1) gs ;;
let () = List.iter (fun g -> Printf.printf "r1:%d\n" (g.gmeth 2)) gs ;;

let gs = List.map (fun g -> g.new_g 1) gs ;;
let () = List.iter (fun g -> Printf.printf "r2:%d\n" (g.gmeth 2)) gs ;;

The output will be:

r1:8
r1:11

r2:9
r2:13

A generic wrapper function can then be written from the code above, which will translate every object of type o into the generic type g with no 'a type parameters.


let wrap_g o =
  let rec new_g self =
    (fun v ->
      let self = o.new_o self v in
      {
        gmeth = o.meth self;
        new_g = new_g self;
      }
    )
  in
  let g = {
    gmeth = o.meth o.self;
    new_g = new_g o.self;
  } in
  (g)

let g1 = wrap_g o1
let g2 = wrap_g o2

let gs = [ g1; g2; ] ;;