Skip to content

Instantly share code, notes, and snippets.

@btbytes
Created December 23, 2015 19:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save btbytes/5cb1a035f9a70f8be403 to your computer and use it in GitHub Desktop.
Save btbytes/5cb1a035f9a70f8be403 to your computer and use it in GitHub Desktop.
Ocaml's unusual object system in a nutshell
(* 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