Created
December 23, 2015 19:38
-
-
Save btbytes/5cb1a035f9a70f8be403 to your computer and use it in GitHub Desktop.
Ocaml's unusual object system in a nutshell
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(* Source: http://pastebin.com/YsUs4Kfa *) | |
(* ocaml's object system is quite unique and rarely used, despite it actually being pretty awesome | |
* I call it "statically typed duck typing" because that's what it is | |
*) | |
(* let's define a """class""" | |
* your basic example of a linked-list | |
*) | |
let node (x, y) = object | |
method hd = x | |
method tl = y | |
method nth n = | |
if n = 0 then | |
x | |
else | |
y#nth (n - 1) | |
method length = | |
1 + y#length | |
end | |
(* this isn't actually a class, this is a function called node that takes two arguments, and returns an object | |
* objects can easily be created anonymously, in this case we simply create an anonynmous object inside the function body | |
* this has methods and closes over the function arguments, this object is immutable | |
* as such, there is really no "this/self" keyword used in this approach | |
* this is your basic recursive implementation of a linked list's head, tail, indexing and length operations | |
*) | |
(*now, the type of this constructor is quite something and automatically inferred by the compiler and statically checked: | |
* | |
* node : | |
* 'a * (< length : int; nth : int -> 'a; .. > as 'b) -> | |
* < hd : 'a; length : int; nth : int -> 'a; tl : 'b > | |
* | |
* I've put it on two lines to make it clearer, the first is the input type. The second argument is the interesting one: | |
* < length : int; nth : int -> 'a; .. > as 'b | |
* | |
* this reads an object with: | |
* - a "length" method that returns an int | |
* - a "nth" method that takes an int and returns something of the same type as the first argument to node. | |
* - ANY FURTHER METHODS, this is what the dots indicate which is very important | |
* | |
* the quote before "a" means this is a type variable. | |
* This means that both instances of 'a can be any type, as long as they are the same one | |
* | |
* then the output type is our object. Which has all our methods, | |
* the most important one is that its "tl" method returns 'b, which we gave as a shorhand to the type of our second argument. | |
* obviously we return the tail we gave as second argument in tact. | |
*) | |
(* we wil also make a nil object | |
* this is not a function, OCaml just uses similar syntax to declare constants and functions | |
* note that we give it hd and tl and nth methods, but raise exceptions on them. | |
* This is done on purpose to give it the appropriate types. | |
* After all. Our "node" constructor demanded that its second argument implement the "nth" and "length" methods. | |
*) | |
exception Empty_list | |
let nil = object | |
method hd = raise Empty_list | |
method tl = raise Empty_list | |
method nth n = raise Empty_list | |
method length = 0 | |
end | |
(* so now we can make some linked lists | |
* probably not the most convenient syntax but you get the point | |
* this will all type check, all the numbers are the same type, which is what the implementation demanded | |
*) | |
let int_list = node(0, node(1, node(2, node(3, nil)))) | |
let () = Printf.printf "%d\n" int_list#length (* 3 *) | |
(* the constructors are polymorphic, | |
* the elements of the list can be any type, as long as they are all the same type. | |
*) | |
let string_list = node("zero", node("one", node("two", node("three", nil)))) | |
let () = Printf.printf "%d\n" string_list#tl#length (* 2 *) | |
(* but this would be a static typing error, | |
* | |
* let hetero_list = node(0, node("one", node(2.0, node(`Three, nil)))) | |
*) | |
(* now, here it gets interesting. | |
* Remember, all our implementation of the linked list wanted was | |
* that the second argument implemented "nth" and "length" correctly. | |
* so here's a fake parametic "structure" that is filled with factorials | |
*) | |
let weird_object = object | |
val internal_array = [| 1,2,3,4 |] | |
(* when we ask for the nth element, it in fact computes the factorial of said number *) | |
method nth n = | |
let rec factorial n = | |
if n = 0 then | |
1 | |
else | |
n * factorial (n - 1) | |
in factorial n | |
method length = 60 | |
end | |
(* this array does not implement the hd and tl methods | |
* but our linked list implementation didn't ask for that for its second elements | |
* so, funnily enough, this works: | |
*) | |
let wtf_structure = node(0, node(4, weird_object)) | |
(* and so does this | |
*) | |
let () = Printf.printf "%d\n" wtf_structure#length (* 62 *) | |
let () = Printf.printf "%d\n" (wtf_structure#nth 8) (* 720 *) | |
(* but say we wanted to implement map on our custom list: | |
*) | |
let rec map_obj f obj = | |
if obj#length = 0 then | |
obj | |
else | |
node (f obj#hd, map_obj f obj#tl) | |
(* so will this now work on our wtf_structure? | |
* No! because the type of this function is inferred by the implementation that the second argument is: | |
* | |
* (< hd : 'a; length : int; nth : int -> 'a; tl : 'b > as 'b) | |
* | |
* the "as 'b" part is important. | |
* It means that the "tl" method must in this case return an object which has all the methods listed there too. | |
* and that's something our "weird object" doesn't provide. | |
*) | |
(* conclusion | |
* OCaml's object system allows for "statically typed duck typing",. | |
* meaning that the type of an object is just its methods and thier types | |
* this system is surprisingly flexible. It statically checks if stuff quacks the right way. | |
* | |
* and yes, with explicit type annotations you can actually give a set of methods together a name | |
* and there are even classes if you want them | |
*) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment