(* 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

" 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

" 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

" wtf_structure # length (* 62 *)

let ( ) = Printf . printf "%d

" ( 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