Previous Next Contents

Objects in Caml

(Chapter written by Jérôme Vouillon and Didier Rémy)

This chapter gives an overview of the object-oriented features of Objective Caml.

Classes and objects

The class point has one instance variable x and two methods get_x and move. The initial value of the instance variable is given here by the class parameter x_init. The variable x is declared mutable, so the method move can change its value.

# class point x_init =
    val mutable x = x_init
    method get_x = x
    method move d = x <- x + d
  end;;
class point (int) =
  val mutable x : int
  method get_x : int
  method move : int -> unit
end

We now create a new point p, giving the initialization argument 7.

# let p = new point 7;;
val p : point = <obj>
Note that the type of p is point. This is an abbreviation automatically defined by the class definition above. It stands for the object type <get_x : int; move : int -> unit>, listing the methods of class point along with their types.

Let us apply some methods to p:

# p#get_x;;
- : int = 7

# p#move 3;;
- : unit = ()

# p#get_x;;
- : int = 10

The library function Oo.copy makes a shallow copy of an object. Its type is < .. > as 'a -> 'a (which is parsed as (< .. > as 'a) -> 'a). The keyword as in that type binds the type variable 'a to the object type < .. >. Therefore, Oo.copy takes an object with any methods (represented by the ellipsis), and returns an object of the same type. The type of Oo.copy is different from type < .. > -> < .. > as each ellipsis represents a different set of methods. Ellipsis actually behaves as a type variable.

# let q = Oo.copy p;;
val q : point = <obj>

# q#move 7; (p#get_x, q#get_x);;
- : int * int = 10, 17

Objects can be compared using the generic comparison functions (=, <, ...). Two objects are equal if and only if they are physically equal. In particular, an object and its copy are not equal.

# let q = Oo.copy p;;
val q : point = <obj>

# p = q, p = p;;
- : bool * bool = false, true

Inheritance

We now define a new class colored_point. This class inherits from class point. So, it has all the instance variable and all the methods of point, plus a new instance variable c and a new method color.

# class colored_point x (c : string) =
    inherit point x
    val c = c
    method color = c
  end;;
class colored_point (int) (string) =
  val c : string
  val mutable x : int
  method color : string
  method get_x : int
  method move : int -> unit
end

# let p' = new colored_point 5 "red";;
val p' : colored_point = <obj>

# p'#get_x, p'#color;;
- : int * string = 5, "red"

A point and a colored point have incompatible types: a point has no method color. However, the function get_x below is a generic function applying method get_x to any object p that has this method (and possibly some others, which are represented by an ellipsis in the type). Thus, it applies to both points and colored points.

# let get_succ_x p = p#get_x + 1;;
val get_succ_x : < get_x : int; .. > -> int = <fun>

# get_succ_x p + get_succ_x p';;
- : int = 17
Methods need not be declared previously, as shown by the example:
# let set_x p = p#set_x;;
val set_x : < set_x : 'a; .. > -> 'a = <fun>

# let incr p = set_x p (get_succ_x p);;
val incr : < get_x : int; set_x : int -> 'a; .. > -> 'a = <fun>

Parameterized classes

Reference cells can also be implemented as objects. The naive definition fails to typecheck:

# class ref x_init = 
    val mutable x = x_init
    method get = x
    method set y = x <- y
  end;;
Characters 5-85:
The type variable 'a is not bound in implicit type definition
  ref = < get : 'a; set : 'a -> unit >
It should be captured by a class type parameter
The reason is that at least one of the methods has a polymorphic type (here, the type of the value stored in the reference cell), thus the class should be parametric. A monomorphic instance of the class could be defined by:
# class ref (x_init:int) = 
    val mutable x = x_init
    method get = x
    method set y = x <- y
  end;;
class ref (int) =
  val mutable x : int
  method get : int
  method set : int -> unit
end
A class for polymorphic references must explicitly list the type parameters in its declaration. The type parameters must also be bound somewhere in the class body by a type constraint.
# class 'a ref x_init = 
    val mutable x = (x_init : 'a)
    method get = x
    method set y = x <- y
  end;;
class 'a ref ('a) =
  val mutable x : 'a
  method get : 'a
  method set : 'a -> unit
end

# let r = new ref 1 in r#set 2; (r#get);;
- : int = 2
The type parameter in the declaration may actually be constrained in the body of the class definition. In the class type, the actual value of the type parameter is displayed in the constraint clause.
# class 'a ref (x_init:'a) = 
    val mutable x = x_init + 1
    method get = x
    method set y = x <- y
  end;;
class 'a ref ('a) =
  constraint 'a = int
  val mutable x : int
  method get : int
  method set : int -> unit
end

Let us consider a more realistic example. We put an additional type constraint in method move, since no free variables must remain uncaptured by a type parameter.

# class 'a circle (c : 'a) =
    val mutable center = c
    method center = center
    method set_center c = center <- c
    method move = (center#move : int -> unit)
  end;;
class 'a circle ('a) =
  constraint 'a = < move : int -> unit; .. >
  val mutable center : 'a
  method center : 'a
  method move : int -> unit
  method set_center : 'a -> unit
end

An alternate definition of circle, using a constraint clause in the class definition, is shown below. The type #point used below in the constraint clause is an abbreviation produced by the definition of class point. This abbreviation unifies with the type of any object belonging to a subclass of class point. It actually expands to < get_x : int; move : int -> unit; .. >. This leads to the following alternate definition of circle, which has slightly stronger constraints on its argument, as we now expect center to have a method get_x.

# class 'a circle (c : 'a) =
    constraint 'a = #point
    val mutable center = c
    method center = center
    method set_center c = center <- c
    method move = center#move
  end;;
class 'a circle ('a) =
  constraint 'a = #point
  val mutable center : 'a
  method center : 'a
  method move : int -> unit
  method set_center : 'a -> unit
end

The class colored_circle is a specialized version of class circle which requires the type of the center to unify with #colored_point, and adds a method color.

# class 'a colored_circle c =
    constraint 'a = #colored_point
    inherit ('a) circle c
    method color = center#color
  end;;
class 'a colored_circle ('a) =
  constraint 'a = #colored_point
  val mutable center : 'a
  method center : 'a
  method color : string
  method move : int -> unit
  method set_center : 'a -> unit
end

Reference to self

A method can also send messages to the object that invoked the method. For that, self must be explicitly bound, here to the variable s.

# class printable_point y as s =
    inherit point y
    method print = print_int s#get_x
  end;;
class printable_point (int) =
  val mutable x : int
  method get_x : int
  method move : int -> unit
  method print : unit
end

# let p = new printable_point 7;;
val p : printable_point = <obj>

# p#print;;
7- : unit = ()
The variable s is bound at the invocation of a method. In particular, if the class printable_point is inherited, the variable s will correctly be bound to an object of the subclass.

Multiple inheritance

Multiple inheritance is allowed. Only the last definition of a method (or of an instance variable) is kept. Previous definitions of a method can be reused by binding the related ancestor. Below, super is bound to the ancestor printable_point. The name super is not actually a variable and can only be used to select a method as in super#print.

# class printable_colored_point y c as self =
    inherit colored_point y c
    inherit printable_point y as super
    method print =
      print_string "(";
      super#print;
      print_string ", ";
      print_string (self#color);
      print_string ")"
  end;;
class printable_colored_point (int) (string) =
  val c : string
  val mutable x : int
  method color : string
  method get_x : int
  method move : int -> unit
  method print : unit
end

# let p' = new printable_colored_point 7 "red";;
val p' : printable_colored_point = <obj>

# p'#print;;
(7, red)- : unit = ()

Non-mutable objects

It is possible to write a version of class point without assignments on the instance variables. The construct {< ... >} returns a copy of ``self'' (that is, the current object), possibly changing the value of some instance variables.

# class functional_point y =
    val x = y
    method get_x = x
    method move d = {< x = x + d >}
  end;;
class functional_point (int) : 'a =
  val x : int
  method get_x : int
  method move : int -> 'a
end

# let p = new functional_point 7;;
val p : functional_point = <obj>

# p#get_x;;
- : int = 7

# (p#move 3)#get_x;;
- : int = 10

# p#get_x;;
- : int = 7
Note that the type abbreviation functional_point is recursive, which can be seen in the class type of functional_point: the type of self to 'a and 'a appears inside the type of the move method.

Virtual methods

The class comparable below is a template for classes with a binary method leq of type 'a -> bool where the type variable 'a is bound to the type of self. Since this class has a method declared but not defined, it must be flagged virtual and cannot be instantiated (that is, no object of this class can be created). It still defines abbreviations. In particular, #comparable expands to < leq : 'a -> bool; .. > as 'a. We see here that the binder as also allows to write recursive types.

# class virtual comparable () : 'a =
    virtual leq : 'a -> bool
  end;;
class virtual comparable (unit) : 'a = virtual leq : 'a -> bool end

We then define a subclass of comparable that wraps integers as comparable objects. There is a type constraint on the class parameter x as the primitive <= is a polymorphic comparison function in Objective Caml. The inherit clause ensures that the type of objects of this class is an instance of #comparable.

# class int_comparable (x : int) =
    inherit comparable ()
    val x = x
    method x = x
    method leq p = x <= p#x
  end;;
class int_comparable (int) : 'a =
  val x : int
  method leq : 'a -> bool
  method x : int
end

Objects of class int_comparable2 below can also modify the integer they hold. The status of instance variable x is changed. It is now mutable. Note that the type int_comparable2 is not a subtype of type int_comparable, as the self type appears in contravariant position in the type of method leq.

# class int_comparable2 x =
    inherit int_comparable x
    val mutable x
    method set_x y = x <- y
  end;;
class int_comparable2 (int) : 'a =
  val mutable x : int
  method leq : 'a -> bool
  method set_x : int -> unit
  method x : int
end

The function min will return the minimum of any two objects whose type unifies with #comparable. The type of min is not the same as #comparable -> #comparable -> #comparable, as the abbreviation #comparable hides a type variable (an ellipsis). Each occurrence of this abbreviation generates a new variable.

# let min (x : #comparable) y =
    if x#leq y then x else y;;
val min : (#comparable as 'a) -> 'a -> 'a = <fun>
This function can be applied to objects of type int_comparable or int_comparable2.
# (min (new int_comparable  7) (new int_comparable 11))#x;;
- : int = 7

# (min (new int_comparable2 5) (new int_comparable2 3))#x;;
- : int = 3

Protected methods

Protected methods are methods that do not appear in object interfaces. They can only be invoked from other methods of the same object.

# class restricted_point x_init as self =
    val mutable x = x_init
    method get_x = x
    method protected move d = x <- x + d
    method bump = self#move 1
  end;;
class restricted_point (int) =
  val mutable x : int
  method bump : unit
  method get_x : int
  method protected move : int -> unit
end

# let p = new restricted_point 0;;
val p : restricted_point = <obj>

# p#move 10;;
Characters 0-1:
This expression has no method move

# p#bump;;
- : unit = ()
Protected methods are inherited. They can be hidden by signature matching, as described in the next section.

Class interfaces

Class interfaces are inferred from class definitions. They may also be defined directly in interfaces of modules. For instance, the following is the interface of a module defining class restricted_point.

# module type POINT = sig 
    class restricted_point (int) =
      val mutable x : int
      method get_x : int
      method protected move : int -> unit
      method bump : unit
   end 
  end;;
module type POINT =
  sig
    class restricted_point (int) =
      val mutable x : int
      method bump : unit
      method get_x : int
      method protected move : int -> unit
    end
  end

# module Point : POINT = struct 
    class restricted_point x = inherit restricted_point x end
  end;;
module Point : POINT
It is sometime necessary to restrict the interface of classes. Instance variables and protected methods can be hidden by signature matching, as shown below. However, public methods cannot be hidden.
# module type ABSPOINT = sig 
    class restricted_point (int) =
      method get_x : int
      method bump : unit
   end 
  end;;
module type ABSPOINT =
  sig
    class restricted_point (int) = method bump : unit method get_x : int end
  end

# module Abspoint : ABSPOINT = Point;;
module Abspoint : ABSPOINT

Using coercions

Subtyping is never implicit. There are, however, two ways to perform subtyping. The most general construction is fully explicit: both the domain and the codomain of the type coercion must be given.

We have seen that points and colored points have incompatible types. For instance, they cannot be mixed in the same list. However, a colored point can be coerced to a point, hiding its color method:

# let colored_point_to_point cp = (cp : colored_point :> point);;
val colored_point_to_point : colored_point -> point = <fun>

# let p = new point 3 and q = new colored_point 4 "blue";;
val p : point = <obj>
val q : colored_point = <obj>

# let l = [p; (colored_point_to_point q)];;
val l : point list = [<obj>; <obj>]
An object of type t can be seen as an object of type t' only if t is a subtype of t'. For instance, a point cannot be seen as a colored point.
# (p : point :> colored_point);;
Characters 0-28:
Type point = < get_x : int; move : int -> unit > is not a subtype of type
  colored_point = < get_x : int; move : int -> unit; color : string >
Indeed, backward coercions are unsafe, and should be combined with a type case, possibly raising a runtime error. However, there is not such operation available in the language. Be aware that subtyping and inheritance are not related. Inheritance is a syntactic relation between classes while subtyping is a semantic relation between types. For instance, the class of colored points could have been defined directly, without inheriting from the class of points; the type of colored points would remain unchanged and thus still be a subtype of points. Conversely, the class int_comparable inherits from class comparable, but type int_comparable is not a subtype of comparable.
# function x -> (x : int_comparable :> comparable);;
Characters 14-48:
Type int_comparable = < leq : int_comparable -> bool; x : int >
is not a subtype of type comparable = < leq : comparable -> bool >
Type int_comparable -> bool is not a subtype of type comparable -> bool
Type comparable = < leq : comparable -> bool > is not a subtype of type
  int_comparable = < leq : int_comparable -> bool; x : int >
Indeed, an object p of class int_comparable has a method leq that expects an argument of type int_comparable since it accesses its x method. Considering p of type comparable would allow to call method leq on p with an argument that does not have a method x, which would be an error.

The domain of a coercion can usually be omitted. For instance, one can define:

# let to_point cp = (cp :> point);;
val to_point : < get_x : int; move : int -> unit; .. > -> point = <fun>
In this case, the function colored_point_to_point is an instance of the function to_point. This is not always true, however. The fully explicit coercion is more precise and is sometimes unavoidable. Here is an example:
# class virtual c () = virtual m : c end;;
class virtual c (unit) = virtual m : c end

# class c' () as self =
    inherit c ()
    method m = (self :> c)
    method m' = 1
  end;;
Characters 51-55:
This expression cannot be coerced to type c = < m : c >; it has type
  < m : c; m' : 'a; .. >
but is here used with type < m : 'b; m' : 'a; .. > as 'b
Type c = < m : c > is not compatible with type 'b
The type of the coercion to type c can be seen here:
# function x -> (x :> c);;
- : (< m : 'a; .. > as 'a) -> c = <fun>
As class c' inherits from class c, its method m must have type c. On the other hand, in expression (self :> c) the type of self and the domain of the coercion above must be unified. That is, the type of the method m in self (i.e. c) is also the type of self. So, the type of self is c. This is a contradiction, as the type of self has a method m', whereas type c does not.

The desired coercion of type <m : c;..> -> c can be obtained by using a fully explicit coercion:

# function x -> (x : #c :> c);;
- : #c -> c = <fun>
Thus one can define class c' as follows:
# class c' () as self =
    inherit c ()
    method m = (self : #c :> c)
    method m' = 1
  end;;
class c' (unit) = method m : c method m' : int end
An alternative is to define class c as follows (of course this definition is not equivalent to the previous one):
# class virtual c () : 'a = virtual m : 'a end;;
class virtual c (unit) : 'a = virtual m : 'a end
Then, a coercion operator is not even required.
# class c' () as self =
    inherit c ()
    method m = self
    method m' = 1
  end;;
class c' (unit) : 'a = method m : 'a method m' : int end
Here, the simple coercion operator (e :> c) can be used to coerce an object expression e from type c' to type c. Semi implicit coercions are actually defined so as to work correctly with classes returning self.
# (new c' () :> c);;
- : c = <obj>

Another common problem may occur when one tries to define a coercion to a class c inside the definition of class c. The problem is due to the type abbreviation not being completely defined yet, and so its subtypes are not clearly known. Then, a coercion (_ : #c :> c) is taken to be the identity function, as in

# function x -> (x :> 'a);;
- : 'a -> 'a = <fun>
As a consequence, if the coercion is applied to self, as in the following example, the type of self is unified with the closed type c. This constrains the class to be closed:
# class c () as self = method m = (self : #c :> c) end;;
Characters 32-48:
Type #c = < m : 'a; .. > is not a subtype of type c = < m : 'b; .. >
Although declaring the class as closed will typecheck, this is not usually what is desired.
# class closed c () as self = method m = (self : #c :> c) end;;
Characters 39-55:
Type < m : 'b; .. > as 'a is not a subtype of type c = 'a
This problem can sometimes be avoided by first defining the abbreviation, using a virtual class:
# class virtual c0 () = virtual m : c0 end;;
class virtual c0 (unit) = virtual m : c0 end

# class c () as self = method m = (self : #c0 :> c0) end;;
class c (unit) = method m : c0 end
The class c may be also declared to inherit from the virtual class c0, so as to simultaneously enforce all methods of c to have the same type as the methods of c0.
# class c () as self = inherit c0 () method m = (self : #c0 :> c0) end;;
class c (unit) = method m : c0 end
One could think of defining the type abbreviation directly:
# type c1 = <m : c1>;;
type c1 = < m : c1 >
However, the abbreviation #c1 cannot be defined this way (the abbreviation #c0 is defined from the class c0, not from the type c0), and should be expanded:
# class c () as self =  method m = (self : <m : c1; ..> as 'a :> c1) end;;
class c (unit) = method m : c1 end

Recursive classes

Recursive classes can be used to define objects whose types are mutually recursive.

# class window () =
    val mutable top_widget = (None : widget option)
    method top_widget = top_widget
  and widget (w : window) =
    val window = w
    method window = window
  end;;
class window (unit) =
  val mutable top_widget : widget option
  method top_widget : widget option
end
class widget (window) = val window : window method window : window end
Although their types are mutually recursive, the classes widget and window are themselves independent.

Simple modules as classes

There is sometime an alternative between using modules or classes. Indeed, there are situations when the two approaches are quite similar. For instance, a stack can be straightforwardly implemented as a class:

# exception Empty;;
exception Empty

# class 'a stack () =
    val mutable l = ([] : 'a list)
    method push x = l <- x::l
    method pop = match l with [] -> raise Empty | a::l' -> l <- l'; a
    method clear = l <- []
    method length = List.length l
  end;;
class 'a stack (unit) =
  val mutable l : 'a list
  method clear : unit
  method length : int
  method pop : 'a
  method push : 'a -> unit
end

However, writing a method for iterating over a stack is more problematic. A method fold would have type ('b -> 'a -> 'b) -> 'b -> 'b. Here 'a is the parameter of the stack. The parameter 'b is not related to the class 'a stack but to the argument that will be passed to the method fold. The intuition is that method fold should be polymorphic, i.e. of type All ('a) ('b -> 'a -> 'b) -> 'b -> 'b, which is not currently possible. One possibility would be to make 'b an extra parameter of class stack

# class ('a, 'b) stack2 () =
    inherit ('a) stack ()
    method fold f (x : 'b) = List.fold_left f x l
  end;;
class ('a, 'b) stack2 (unit) =
  val mutable l : 'a list
  method clear : unit
  method fold : ('b -> 'a -> 'b) -> 'b -> 'b
  method length : int
  method pop : 'a
  method push : 'a -> unit
end
However, method fold of a given object can only be applied to functions that all have the same type:
# let s = new stack2 ();;
val s : ('_a, '_b) stack2 = <obj>

# s#fold (+) 0;;
- : int = 0

# s;;
- : (int, int) stack2 = <obj>
The best solution would be to make method fold polymorphic. However Ocaml does not currently allow methods to be polymorphic.

Thus, the current solution is to leave the function fold outside of the class.

# class 'a stack3 () =
    inherit ('a) stack ()
    method iter f = List.iter (f : 'a -> unit) l
  end;;
class 'a stack3 (unit) =
  val mutable l : 'a list
  method clear : unit
  method iter : ('a -> unit) -> unit
  method length : int
  method pop : 'a
  method push : 'a -> unit
end

# let stack_fold (s : 'a #stack3) f x =
    let accu = ref x in
    s#iter (fun e -> accu := f !accu e);
    !accu;;
val stack_fold : 'a #stack3 -> ('b -> 'a -> 'b) -> 'b -> 'b = <fun>

Implementing sets leads to another difficulty. Indeed, the method union needs to be able to access the internal representation of another object of the same class. For that, a set class must have an additional method returning this representation. However, this representation should not be public. This result is obtained by making the type of the representation abstract via a module signature constraint. From outside, the additional method appears like a tag ensuring that an object belongs to class set.

# module type SET =
    sig
      type 'a tag
      class 'a c (unit) : 'b =
        method is_empty : bool
        method mem : 'a -> bool
        method add : 'a -> 'b
        method union : 'b -> 'b
        method iter : ('a -> unit) -> unit
        method tag : 'a tag
      end
    end;;
module type SET =
  sig
    type 'a tag
    class 'a c (unit) : 'b =
      method add : 'a -> 'b
      method is_empty : bool
      method iter : ('a -> unit) -> unit
      method mem : 'a -> bool
      method tag : 'a tag
      method union : 'b -> 'b
    end
  end

# module Set : SET =
    struct
      let rec merge l1 l2 =
        match l1 with
          [] -> l2
        | h1 :: t1 ->
            match l2 with
              [] -> l1
            | h2 :: t2 ->
                if h1 < h2 then h1 :: merge t1 l2
                else if h1 > h2 then h2 :: merge l1 t2
                else merge t1 l2
      type 'a tag = 'a list
      class 'a c () : 'b =
        val repr = ([] : 'a list)
        method is_empty = (repr = [])
        method mem x = List.exists ((=) x) repr
        method add x = {< repr = merge [x] repr >}
        method union (s : 'b) = {< repr = merge repr s#tag >}
        method iter (f : 'a -> unit) = List.iter f repr
        method tag = repr
      end
    end;;
module Set : SET

Previous Next Contents