With abstract classes, you can factorize code and group their subclasses into one ``communication protocol''. An abstract class fixes the names and types of messages that may be received by instances of child classes. This technique will be better appreciated in connection with multiple inheritance.

The notion of an open object type (or simply an open type) that specifies the required methods allows code to work with instances using generic functions. But you may need to make the type constraints precise; this will be necessary for parameterized classes, which provide the genericity of parameterized polymorphism in the context of classes. With this latter object layer feature, Objective CAML can really be generic.

A class

When a subclass of an abstract class redefines

As an example, suppose we want to construct a set of displayable objects, all with a method

We note that the abstractness of the class and of its method

#class

virtual`printable`

`()`

`=`

object(self)

method

virtual`to_string`

`:`

`unit`

`->`

`string`

method`()`

`=`

`print_string`

(self#to_string())

end`;;`

`class virtual printable :`

`unit ->`

`object`

`method print : unit -> unit`

`method virtual to_string : unit -> string`

`end`

From this class, let us try to define the class hierarchy of figure 15.4.

It is easy to redefine the classes

Figure 15.4: Relations between classes of displayable objects.

#let`p`

`=`

new`point`

(`1`

`,`

`1`

)

in`p#print()`

`;;`

`( 1, 1)- : unit = ()`

#let`pc`

`=`

new`colored_point`

(`2`

`,`

`2`

)

`"blue"`

in`pc#print()`

`;;`

`( 2, 2) with color blue- : unit = ()`

#let`t`

`=`

new`picture`

`3`

in`t#add`

(new`point`

(`1`

`,`

`1`

))`;`

`t#add`

(new`point`

(`3`

`,`

`2`

))`;`

`t#add`

(new`point`

(`1`

`,`

`4`

))`;`

`t#print()`

`;;`

`[ ( 1, 1) ( 3, 2) ( 1, 4)]- : unit = ()`

Subclass

#class`rectangle`

(p1`,`

p2)

`=`

object

inherit`printable`

`()`

val`llc`

`=`

(p1

`:`

`point`

)

val`urc`

`=`

(p2

`:`

`point`

)

method`to_string`

`()`

`=`

`"["`

`^`

`llc#to_string()`

`^`

`","`

`^`

`urc#to_string()`

`^`

`"]"`

end`;;`

`class rectangle :`

`point * point ->`

`object`

`val llc : point`

`val urc : point`

`method print : unit -> unit`

`method to_string : unit -> string`

`end`

Class

#let`r`

`=`

new`rectangle`

(new`point`

(`2`

`,`

`3`

)`,`

new`point`

(`4`

`,`

`5`

));;`val r : rectangle = <obj>`

# r#print();;`[( 2, 3),( 4, 5)]- : unit = ()`

point = < distance : unit -> float; get_x : int; get_y : int; moveto : int * int -> unit; rmoveto : int * int -> unit; to_string : unit -> string >This is a closed type; that is, all methods and associated types are fixed. No additional methods and types are allowed. Upon a class declaration, the mechanism of type inference computes the closed type associated with class.

#let`f`

`x`

`=`

`x#get_x`

`;;`

`val f : < get_x : 'a; .. > -> 'a = <fun>`

The type inferred for the argument of

# f

(new`point`

(`2`

`,`

`3`

))`;;`

`- : int = 2`

# f

(new`colored_point`

(`2`

`,`

`3`

)

`"emerald"`

)`;;`

`- : int = 2`

#class`c`

`()`

`=`

object

method`get_x`

`=`

`"I have a method get_x"`

end`;;`

`class c : unit -> object method get_x : string end`

# f

(new`c`

`()`

)`;;`

`- : string = "I have a method get_x"`

Type inference for classes may generate open types, particularly for initial values in instance construction. The following example constructs a class

The types of both

#class`couple`

(a`,`

b)

`=`

object

val`p0`

`=`

`a`

val`p1`

`=`

`b`

method`to_string()`

`=`

`p0#to_string()`

`^`

`p1#to_string()`

method`copy`

`()`

`=`

new`couple`

(p0`,`

p1)

end`;;`

`class couple :`

`(< to_string : unit -> string; .. > as 'a) *`

`(< to_string : unit -> string; .. > as 'b) ->`

`object`

`val p0 : 'a`

`val p1 : 'b`

`method copy : unit -> couple`

`method to_string : unit -> string`

`end`

We use the sharp symbol to indicate the open type built from a closed type

The type obtained contains all of the methods of type

Which allows us to write:

The type constraint with

#let`g`

(x

`:`

`#point`

)

`=`

`x#message;;`

`val g :`

`< distance : unit -> float; get_x : int; get_y : int; message : 'a;`

`moveto : int * int -> unit; print : unit -> unit;`

`rmoveto : int * int -> unit; to_string : unit -> string; .. > ->`

`'a = <fun>`

Just as in the rest of the language, the object extension of Objective CAML provides static typing through inference. When this mechanism does not have enough information to determine the type of an expression, a type variable is assigned. We have just seen that this process is also valid for typing objects; however, it sometimes leads to ambiguous situations which the user must resolve by explicitly giving type information.

#class`a_point`

`p0`

`=`

object

val`p`

`=`

`p0`

method`to_string()`

`=`

`p#to_string()`

end`;;`

`Characters 6-89:`

`Some type variables are unbound in this type:`

`class a_point :`

`(< to_string : unit -> 'b; .. > as 'a) ->`

`object val p : 'a method to_string : unit -> 'b end`

`The method to_string has type unit -> 'a where 'a is unbound`

We resolve this ambiguity by saying that parameter

#class`a_point`

(p0

`:`

`#point`

)

`=`

object

val`p`

`=`

`p0`

method`to_string()`

`=`

`p#to_string()`

end`;;`

`class a_point :`

`(#point as 'a) -> object val p : 'a method to_string : unit -> string end`

In order to set type constraints in several places in a class declaration, the following syntax is used:

The above example can be written specifying that parameter

#class`a_point`

(p0

`:`

'a)

`=`

object

constraint

'a

`=`

`#point`

val`p`

`=`

`p0`

method`to_string()`

`=`

`p#to_string()`

end`;;`

`class a_point :`

`(#point as 'a) -> object val p : 'a method to_string : unit -> string end`

Several type constraints can be given in a class declaration.

An open type cannot appear as the type of a method.

This strong restriction exists because an open type contains an uninstantiated type variable coming from the rest of the type. Since one cannot have a free variable type in a type declaration, a method containing such a type is rejected by type inference.

#class`b_point`

`p0`

`=`

object

inherit`a_point`

`p0`

method`get`

`=`

`p`

end`;;`

`Characters 6-77:`

`Some type variables are unbound in this type:`

`class b_point :`

`(#point as 'a) ->`

`object val p : 'a method get : 'a method to_string : unit -> string end`

`The method get has type #point where .. is unbound`

In fact, due to the constraint ``

` `

` `

`=`

` `

#point'', the type of
`..`

), which is not allowed.The type of method

#class`point_eq`

(x`,`

y)

`=`

object

(self

`:`

'a)

inherit`point`

(x`,`

y)

method`eq`

(p`:`

'a)

`=`

(self#get_x

`=`

`p#get_x`

)

`&&`

(self#get_y

`=`

`p#get_y`

)

end`;;`

`class point_eq :`

`int * int ->`

`object ('a)`

`val mutable x : int`

`val mutable y : int`

`method distance : unit -> float`

`method eq : 'a -> bool`

`method get_x : int`

`method get_y : int`

`method moveto : int * int -> unit`

`method print : unit -> unit`

`method rmoveto : int * int -> unit`

`method to_string : unit -> string`

`end`

You can inherit from the class

#class`colored_point_eq`

(xc`,`

yc)`c`

`=`

object

(self

`:`

'a)

inherit`point_eq`

(xc`,`

yc)

as`super`

val`c`

`=`

(c`:`

string)

method`get_c`

`=`

`c`

method`eq`

(pc

`:`

'a)

`=`

(self#get_x

`=`

`pc#get_x`

)

`&&`

(self#get_y

`=`

`pc#get_y`

)

`&&`

(self#get_c

`=`

`pc#get_c`

)

end`;;`

`class colored_point_eq :`

`int * int ->`

`string ->`

`object ('a)`

`val c : string`

`val mutable x : int`

`val mutable y : int`

`method distance : unit -> float`

`method eq : 'a -> bool`

`method get_c : string`

`method get_x : int`

`method get_y : int`

`method moveto : int * int -> unit`

`method print : unit -> unit`

`method rmoveto : int * int -> unit`

`method to_string : unit -> string`

`end`

The method

Let us define the abstract class

#class

virtual`geometric_object`

`()`

`=`

object

method

virtual`compute_area`

`:`

`unit`

`->`

`float`

method

virtual`compute_peri`

`:`

`unit`

`->`

`float`

end;;

Then we redefine class

#class`rectangle_1`

((p1`,`

p2)

`:`

'a)

`=`

object

constraint

'a

`=`

`point`

`*`

`point`

inherit`printable`

`()`

inherit`geometric_object`

`()`

val`llc`

`=`

`p1`

val`urc`

`=`

`p2`

method`to_string`

`()`

`=`

`"["`

`^`

llc#to_string()`^`

`","`

`^`

urc#to_string()`^`

`"]"`

method`compute_area()`

`=`

`float`

(`abs`

(urc#get_x

`-`

`llc#get_x`

)

`*`

`abs`

(urc#get_y

`-`

`llc#get_y`

))

method`compute_peri()`

`=`

`float`

(

(abs(urc#get_x

`-`

`llc#get_x`

)

`+`

`abs`

(urc#get_y

`-`

`llc#get_y`

))

`*`

`2`

)

end;;`class rectangle_1 :`

`point * point ->`

`object`

`val llc : point`

`val urc : point`

`method compute_area : unit -> float`

`method compute_peri : unit -> float`

`method print : unit -> unit`

`method to_string : unit -> string`

`end`

This implementation of classes respects the inheritance graph of figure 15.5.

In order to avoid rewriting the methods of class

Figure 15.5: Multiple inheritance.

In such a case, only the abstract methods of the abstract class

Figure 15.6: Multiple inheritance (continued).

#class`rectangle_2`

(p2

`:`

'a)

`=`

object

constraint

'a

`=`

`point`

`*`

`point`

inherit`rectangle`

`p2`

inherit`geometric_object`

`()`

method`compute_area()`

`=`

`float`

(`abs`

(urc#get_x

`-`

`llc#get_x`

)

`*`

`abs`

(urc#get_y

`-`

`llc#get_y`

))

method`compute_peri()`

`=`

`float`

(

(abs(urc#get_x

`-`

`llc#get_x`

)

`+`

`abs`

(urc#get_y

`-`

`llc#get_y`

))

`*`

`2`

)

end;;

Continuing in the same vein, the hierarchies

If we assume that classes

Figure 15.7: Multiple inheritance (end).

In the case where methods of the same type exist in both classesclass`rectangle_3`

(p1`,`

p2)

`=`

inherit`printable_rect`

(p1`,`

p2)

as`super_print`

inherit`geometric_rect`

(p1`,`

p2)

as`super_geo`

end;;

Multiple inheritance allows factoring of the code by integrating methods already written from various parent classes to build new entities. The price paid is the size of constructed objects, which are bigger than necessary due to duplicated fields, or inherited fields useless for a given application. Furthermore, when there is duplication (as in our last example), communication between these fields must be established manually (update, etc.). In the last example for class

The syntax differs slightly from the declaration of parameterized types; type parameters are between brackets.

The Objective CAML type is noted as usual:

For instance, if a class

#class`pair`

`x0`

`y0`

`=`

object

val`x`

`=`

`x0`

val`y`

`=`

`y0`

method`fst`

`=`

`x`

method`snd`

`=`

`y`

end`;;`

`Characters 6-106:`

`Some type variables are unbound in this type:`

`class pair :`

`'a ->`

`'b -> object val x : 'a val y : 'b method fst : 'a method snd : 'b end`

`The method fst has type 'a where 'a is unbound`

One again gets the typing error mentioned when class

As in the case of parameterized types, it is necessary to parameterize class

Type inference displays a class interface parameterized by variables of type

#class

`[`

'a`,`

'b`]`

`pair`

(x0`:`

'a)

(y0`:`

'b)

`=`

object

val`x`

`=`

`x0`

val`y`

`=`

`y0`

method`fst`

`=`

`x`

method`snd`

`=`

`y`

end`;;`

`class ['a, 'b] pair :`

`'a ->`

`'b -> object val x : 'a val y : 'b method fst : 'a method snd : 'b end`

When a value of a parameterized class is constructed, type parameters are instantiated with the types of the construction parameters:

#let`p`

`=`

new`pair`

`2`

`'X'`

;;`val p : (int, char) pair = <obj>`

# p#fst;;`- : int = 2`

#let`q`

`=`

new`pair`

`3`

`.`

`1`

`2`

true;;`val q : (float, bool) pair = <obj>`

# q#snd;;`- : bool = true`

In class declarations, type parameters are shown between brackets, but in types, they are shown between parentheses.

#class

`[`

'a`,`

'b`]`

`acc_pair`

(x0

`:`

'a)

(y0

`:`

'b)

`=`

object

inherit

`[`

'a`,`

'b`]`

`pair`

`x0`

`y0`

method`get1`

`z`

`=`

if`x`

`=`

`z`

then`y`

else`raise`

`Not_found`

method`get2`

`z`

`=`

if`y`

`=`

`z`

then`x`

else`raise`

`Not_found`

end;;`class ['a, 'b] acc_pair :`

`'a ->`

`'b ->`

`object`

`val x : 'a`

`val y : 'b`

`method fst : 'a`

`method get1 : 'a -> 'b`

`method get2 : 'b -> 'a`

`method snd : 'b`

`end`

#let`p`

`=`

new`acc_pair`

`3`

true;;`val p : (int, bool) acc_pair = <obj>`

# p#get1

`3`

;;`- : bool = true`

We can make the type parameters of the inherited parameterized class more precise, e.g. for a pair of points.

#class`point_pair`

(p1`,`

p2)

`=`

object

inherit

`[`

point`,`

point`]`

`pair`

`p1`

`p2`

end;;`class point_pair :`

`point * point ->`

`object`

`val x : point`

`val y : point`

`method fst : point`

`method snd : point`

`end`

Class

To build pairs of displayable objects (i.e. having a method

#class`printable_pair`

`x0`

`y0`

`=`

object

inherit

`[`

printable`,`

`printable`

`]`

`acc_pair`

`x0`

`y0`

method`()`

`=`

`x#print();`

`y#print`

`()`

end;;

This implementation allows us to construct pairs of instances of

We could try to open type

This first attempt fails because methods

#class`printable_pair`

(x0

)

(y0

)

`=`

object

inherit

`[`

`#printable`

`,`

`#printable`

`]`

`acc_pair`

`x0`

`y0`

method`()`

`=`

`x#print();`

`y#print`

`()`

end;;`Characters 6-149:`

`Some type variables are unbound in this type:`

`class printable_pair :`

`(#printable as 'a) ->`

`(#printable as 'b) ->`

`object`

`val x : 'a`

`val y : 'b`

`method fst : 'a`

`method get1 : 'a -> 'b`

`method get2 : 'b -> 'a`

`method print : unit -> unit`

`method snd : 'b`

`end`

`The method fst has type #printable where .. is unbound`

So we shall keep the type parameters of the class, while constraining them to the open type

#class

`[`

'a`,`

'b`]`

`printable_pair`

(x0

)

(y0

)

`=`

object

constraint

'a

`=`

`#printable`

constraint

'b

`=`

`#printable`

inherit

`[`

'a`,`

'b`]`

`acc_pair`

`x0`

`y0`

method`()`

`=`

`x#print();`

`y#print`

`()`

end;;`class ['a, 'b] printable_pair :`

`'a ->`

`'b ->`

`object`

`constraint 'a = #printable`

`constraint 'b = #printable`

`val x : 'a`

`val y : 'b`

`method fst : 'a`

`method get1 : 'a -> 'b`

`method get2 : 'b -> 'a`

`method print : unit -> unit`

`method snd : 'b`

`end`

Then we construct a displayable pair containing a point and a colored point.

#let`pp`

`=`

new`printable_pair`

(new`point`

(`1`

`,`

`2`

))

(new`colored_point`

(`3`

`,`

`4`

)

`"green"`

);;`val pp : (point, colored_point) printable_pair = <obj>`

# pp#print();;`( 1, 2)( 3, 4) with color green- : unit = ()`

#let`r`

`=`

new`pair`

`[]`

`[];;`

`val r : ('_a list, '_b list) pair = <obj>`

# r#fst;;`- : '_a list = []`

# r#fst

`=`

`[`

`1`

;`2`

`]`

;;`- : bool = false`

# r;;`- : (int list, '_a list) pair = <obj>`

A parameterized class can also be viewed as a closed object type; therefore nothing prevents us from also using it as an open type with the sharp notation.

#let`compare_nothing`

(`x`

`:`

('a`,`

'a)`#pair`

)

`=`

if`x#fst`

`=`

`x#fst`

then`x#mess`

else`x#mess2;;`

`val compare_nothing :`

`< fst : 'a; mess : 'b; mess2 : 'b; snd : 'a; .. > -> 'b = <fun>`

This lets us construct parameterized types that contain weak type variables that are also open object types.

#let`prettytype`

`x`

(`y`

`:`

('a`,`

'a)`#pair`

)

`=`

if`x`

`=`

`y#fst`

then`y`

else`y;;`

`val prettytype : 'a -> (('a, 'a) #pair as 'b) -> 'b = <fun>`

If this function is applied to one parameter, we get a closure, whose type variables are weak. An open type, such as

#let`g`

`=`

`prettytype`

`3`

;;`val g : ((int, int) _#pair as 'a) -> 'a = <fun>`

Now, if function

# g

(new`acc_pair`

`2`

`3`

);;`- : (int, int) acc_pair = <obj>`

# g;;`- : (int, int) acc_pair -> (int, int) acc_pair = <fun>`

Then we can no longer use

# g

(new`pair`

`1`

`1`

);;`Characters 4-16:`

`This expression has type (int, int) pair = < fst : int; snd : int >`

`but is here used with type`

`(int, int) acc_pair =`

`< fst : int; get1 : int -> int; get2 : int -> int; snd : int >`

`Only the second object type has a method get1`

Finally, since parameters of the parameterized class can also get weakened, we obtain the following example.

#let`h`

`=`

`prettytype`

`[];;`

`val h : (('_b list, '_b list) _#pair as 'a) -> 'a = <fun>`

#let`h2`

`=`

`h`

(new`pair`

`[]`

`[`

`1`

;`2`

`]`

);;`val h2 : (int list, int list) pair = <obj>`

# h;;`- : (int list, int list) pair -> (int list, int list) pair = <fun>`

The type of the parameter of

# h

(new`acc_pair`

`[]`

`[`

`4`

;`5`

`]`

);;`Characters 4-25:`

`This expression has type`

`('a list, int list) acc_pair =`

`< fst : 'a list; get1 : 'a list -> int list; get2 : int list -> 'a list;`

`snd : int list >`

`but is here used with type`

`(int list, int list) pair = < fst : int list; snd : int list >`

`Only the first object type has a method get1`

Parameterized classes of Objective CAML are absolutely necessary as soon as one has methods whose type includes a type variable different from the type of