OCaml: Objects
haifengl.wordpress.com
haifengl.wordpress.com
let bar x = x#info + 1
and OCaml will give a type: val bar : < info : int; .. > -> int = <fun>
which is precisely what we want since we can now define functions on any object that has an info member. At the same time, we can't write let buz x = x.info + 1
because we receive the error Error: Unbound record field info
This is a big deal since the exposed members of objects must be methods in OCaml. Certainly, we can declare values and mutable values, but these are not exposed in the objects type. Specifically, # class foo = object val info = 1 end;;
class foo : object val info : int end
# let bar = new foo;;
val bar : foo = <obj>
# bar#info;;
Error: This expression has type foo
It has no method info
In order to get around this, we can change these values to methods (or add getter methods that return these values). However, the problem with doing that is then we can't make a new object with all of the same values, but with the specified valued changed: # class foo = object method info = 1 end;;
class foo : object method info : int end
# let bar = new foo;;
val bar : foo = <obj>
# bar # info;;
- : int = 1
# let buz = bar with info = 2;;
Error: Parse error: [str_item] or ";;" expected (in [top_phrase])
In other words, we don't have a good mechanism for creating a bunch of immutable objects.In other words, OCaml records allow immutable records, but they lack row polymorphsim. OCaml classes allow row polymorphism, but disallow immutable updates (immutable updates meaning create a new object with everything the same, but one element different.)
Does anyone know why this is the case?
Record fields are simply offsets in a struct, so the type must be fully known when compiling the code. It would not be possible to compile a function that could work on two different types of struct that both happened to have a .info field.
There's a functional update construct for objects:
http://caml.inria.fr/pub/docs/manual-ocaml/objectexamples.ht...
It works like { r with field = value }.
Instead, try taking a look at the store-comonad mechanism I've got below: https://news.ycombinator.com/item?id=9213482
(* 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>OCaml's object system is wonderfully austere. It arises as a pretty natural extension of the more restrained signature subtyping relation---and this helps drive home, as this article also emphasizes, the vital distinction between subclassing and subtyping. That alone means that you'll have a more well-principled and reasonable object system than basically any other one I've ever used.
In day-to-day OCaml, I often hear people suggest that one should avoid the object system. The primary things it buys you are access to the row-typing system and class/object constructor hierarchies. It however is a bit slower due to the use of dynamic dispatch (instead of compiling vtables statically to their use site) and can face some inference issues.
That said, row types are an incredibly useful type construct (often Haskellers lament the fact that they're missing leading to the creation of things like Ermine and Vinyl). Cautious use of the object system is very exciting!
OCaml's object system is certainly awesome but in practice you'll find it is unevenly used -- some never use it, while I'm sure there are others who use it heavily. Because it behaves in a way that is not quite compatible with the rest of the language it is inconvenient to mix APIs that use the two styles of programming. Another comment here on HN links to a post that summarizes with the rule of thumb of simply "don't use objects".
The lesson is that any given feature exists in an ecosystem of other features and its utility is dictated by many things (impedance mismatch with the rest of the language or even language cultural problems) other than the specification itself.
To give a concrete example of a program where I happened to use objects for the first time in ages[1], most of the program works by defining a set of types[2], and then using simple functions to work on those types. The program is essentially: transform the input structure to another structure to another structure. All the structures are publicly defined making it really easy to understand the program and debug when things go wrong. The flow of execution is simple and obvious, and the compiler picks up possible errors in that flow because of the strong typing.
But there happened to be one place that I wanted the ability to hide data + use implicit variant code alongside that data, and then I switched over to using objects[3]. It's a narrow use case and I've written a lot of code without ever needing that.
Of course in OOP languages you essentially have to use objects to get any work done, and so people go crazy and it's impossible to follow the flow of execution.
[1] https://github.com/libguestfs/libguestfs/tree/master/v2v
[2] https://github.com/libguestfs/libguestfs/blob/master/v2v/typ...
[3] https://github.com/libguestfs/libguestfs/blob/master/v2v/typ...
[1] http://cl-cookbook.sourceforge.net/clos-tutorial/index.html
Also, I highly recommend Doug Hoyte's Let Over Lambda[1] where he goes into detail about a certain duality of closures and objects.
"Let Over Lambda is one of the most hardcore computer programming books out there. Starting with the fundamentals, it describes the most advanced features of the most advanced language: Common Lisp. Only the top percentile of programmers use lisp and if you can understand this book you are in the top percentile of lisp programmers. If you are looking for a dry coding manual that re-hashes common-sense techniques in whatever langue du jour, this book is not for you. This book is about pushing the boundaries of what we know about programming. While this book teaches useful skills that can help solve your programming problems today and now, it has also been designed to be entertaining and inspiring. If you have ever wondered what lisp or even programming itself is really about, this is the book you have been looking for."
Hoyte's first intention, writing a 2nd Volume for Paul Grahams On Lisp, which goes into detail about macros, is actually quite good. It just does not live up to "Volume 1".
And about the 1 percentile and "only the best" talk: Lisp and Lisp Macros are not really "magic". They are accessible to programmers like you and me and if someone does not bother to write them it is not about lack of understanding, but lack of time it takes to write them properly.
It was the Austin Meetup. The name of the presenter escapes me, but it was great overview. The topic wasn't meant to be CLOS, it was just mentioned in passing, which was why I was hoping that it was the topic of this article.
One issue with the article - the author confuses duck typing and structural typing. While they are similar, there are important differences. OCaml uses structural typing (which enforces type at compile time) and not duck typing.
(This also seems to be the fourth time the same post was submitted by the same user, with increasingly clickbaity headlines.)
You're right about the submitted headline ("Best object system is actually from a functional language").