This would be a great place to include lenses if OCaml allowed for higher rank polymorphism. I don't think you can jerry-rig a Functor-based lens solution into an object like this...
(* Create a basic lens type *)
type ('a, 'b) t = {
get : 'a -> 'b;
set : 'b -> 'a -> 'a
}
(* A base lens, kind of like a base class *)
type 'a myrecord_lens = {
foo : ('a,int) t;
label : ('a,string) t
}
(* Record 1 *)
type myrecord1 = {
foo : int;
bar : float;
label : string
}
let myrecord1 = {
foo = 1;
bar = 2.3;
label = "myrecord1"
}
let foo_lens = {
get = (fun x -> x.foo);
set = (fun v x -> { x with foo = v })
}
let bar_lens = {
get = (fun x -> x.bar);
set = (fun v x -> { x with bar = v })
}
let label_lens = {
get = (fun x -> x.label);
set = (fun v x -> { x with label = v })
}
let myrecord1_lens = {
foo = foo_lens;
label = label_lens
}
(* Record 2 *)
type myrecord2 = {
foo : int;
buz : string;
label : string
}
let myrecord2 = {
foo = 4;
buz = "five";
label = "myrecord2"
}
let foo_lens = {
get = (fun x -> x.foo);
set = (fun v x -> { x with foo = v })
}
let buz_lens = {
get = (fun x -> x.buz);
set = (fun v x -> { x with buz = v })
}
let label_lens = {
get = (fun x -> x.label);
set = (fun v x -> { x with label = v })
}
let myrecord2_lens = {
foo = foo_lens;
label = label_lens
}
(* Write a generic function to work with these objects*)
let myfunc (myrecord:'a) (lens:'a myrecord_lens) =
let _ = Printf.printf "Working with: %s\n" (lens.label.get myrecord) in
(lens.foo.get myrecord) + 1
(* Get some results *)
let _ = myfunc myrecord1 myrecord1_lens
let _ = myfunc myrecord2 myrecord2_lens
For the important pieces of the output, we have: val myfunc : 'a -> 'a myrecord_lens -> int = <fun>
Working with: myrecord1
- : int = 2
Working with: myrecord2
- : int = 5
Hence, by using lenses, we can basically get the polymorphic behavior that we need out of immutable records. The syntax is pretty verbose, but it can be lessened with macros.As a final note, there is an OCaml lens library to help with this, but you're still mostly stuck writing lenses by hand:
https://github.com/avsm/ocaml-lens
Also, Jane Street has fieldslib, which does some interesting things with record types. It doesn't have exactly what I described above, but it does help in many cases and they have some nice macros to help generate getters and setters:
Truly what you want is the "pre-applied" lens or the store comonad
type ('i, 'j) store = { get : 'j; set : 'j -> 'i };;
Then object types can have something like let o = object
val slot = 3
method slot =
{ get = slot
; set = fun slot' -> {< slot = slot' >}
}
end
which has type val o : < slot : ('a, int) store > as 'a
which is kind of a cool pattern. # o#slot.get;;
- : int = 3
# (o#slot.set 10)#slot.get;;
- : int = 10
# o#slot.set o#slot.get;;
- : < slot : ('a, int) store > as 'a = <obj>