I went in expecting to see a write up on Common Lisp's CLOS[1], because at a recent Clojure Meetup, the speaker put forth the claim that CLOS is the most powerful OO system out there. He didn't have time to validate the claim, but it may be of interest to those who like this article.
I don't think OCaml wants to vie for "most powerful" object system. It's almost the opposite, really. OCaml's object system is "best" because it's weak in exactly the kinds of ways that make it easy to reason about and more likely to be correct.
SICP does the job of demonstrating the Closure-Object relationship just as fine, without boring the reader with endless references to the superiority of Lisp, as Let Over Lambda does.
"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."
I should have known better when I ordered the book.
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.
Thanks for the reference. I will check out that and SICP[1] as mentioned by one of your replies.
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.
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 objects are pretty neat, although I rarely ever end up using them. Although I also rarely ever use OCaml for that matter.
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 post is also a great demonstration as to why a programming language cannot be evaluated as a feature checklist.
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.
I have a slightly more nuanced point of view. I pretty much write OCaml code for a living, and rarely use objects, but that's because they are rarely useful (as a general concept, nothing to do with OCaml).
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.
I'm not a very experienced OCaml developer but after several iterations on a simple game engine in OCaml I found that there were a few places I could significantly reduce boilerplate by using objects. Entities in a game frequently need to carry around their own internal state.
To me the post is really not making the case at all that OCaml's object system is "best", as suggested in the HN headline. Really the only comparison to any other system is contrasting row polymorphism to duck typing. Like another user I was actually expecting this to be about CLOS based on the title. It'd still be a hard claim to make given some of the warts CLOS has, but at least it's a claim that people will often genuinely make.
(This also seems to be the fourth time the same post was submitted by the same user, with increasingly clickbaity headlines.)
What I never understood is why OCaml allows row polymorphism in objects, but not in record types. For example, we can write:
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.)
I believe it's because #info (ie. method access) is kind of dynamic. The string "info" is hashed and then the hash is looked up at runtime against the class vtable. (For precise details, see 19.3.5 here: http://caml.inria.fr/pub/docs/manual-ocaml/intfc.html) I'm not clear under what circumstances this can be optimized to a direct call, and there is also some kind of cache, so repeated method calls to the same method are fast. This is the reason people who value performance over everything else steer clear of using objects in OCaml.
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.
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...
In fact you can, but it becomes a little verbose. Thankfully, much of the verbosity can be eliminated by macros. In any case, here's an example with lenses that shows how to do 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:
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:
> OCaml classes allow row polymorphism, but disallow immutable updates (immutable updates meaning create a new object with everything the same, but one element different.)
There's a functional update construct for objects:
That syntax works for records not objects. The {< _ = _ >} syntax is sort-of equivalent except it can only be used within method definitions (!) and is therefore too restricted.
30 comments
[ 2.6 ms ] story [ 81.1 ms ] thread[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.
[1] http://letoverlambda.com/
"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.
[1] http://mitpress.mit.edu/sicp/
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!
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.
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...
(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").
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.
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:
https://github.com/janestreet/fieldslib
Truly what you want is the "pre-applied" lens or the store comonad
Then object types can have something like which has type which is kind of a cool pattern.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