I’d like to understand the reason for this behavior of OCAML objects. Suppose I have a class A that calls methods of an object of another class B. Schematically, A#f calls B#g and B#h. The normal practice in OOP is that I would like to avoid using B as a fixed concrete class, but instead declare only an interface for B. What is the best way to do this in OCAML? I tried several options, and I do not quite understand the reason why some of them work while others don’t. Here are the code samples.
Version 1:
# class classA = object
method f b = b#g + b#h
end ;;
Error: Some type variables are unbound in this type:
class a : object method f : < g : int; h : int; .. > -> int end
The method f has type (< g : int; h : int; .. > as 'a) -> int where 'a
is unbound
This behavior is well-known: OCAML correctly infers that b has the open object type <g:int;h:int;..> but then complains that my class does not declare any type variables. So it seems that classA is required to have type variables; I then introduced a type variable explicitly.
Version 2:
# class ['a] classA2 = object
method f (b:'a) = b#g + b#h
end ;;
class ['a] classA2 :
object constraint 'a = < g : int; h : int; .. > method f : 'a -> int end
This works, but the class is now explicitly polymorphic with a type constraint, as OCAML shows. It is also confusing that the class type contains a type variable 'a, and yet I can still say let x = new classA2 without specifying a type value for 'a. Why is that possible?
Another drawback of classA2 is that an explicit type constraint (b:'a) contains a type variable. After all, I know that b must conform to a fixed interface rather than to an unknown type 'a. I want OCAML to verify that this interface is indeed correct.
So in version 3 I first declared an interface classB as a class type and then declared that b must be of this type:
# class type classB = object method g:int method h:int end;;
class type classB = object method g : int method h : int end
# class classA3 = object method f (b:classB) = b#g + b#h end;;
class classA3 : object method f : classB -> int end
This works too, but my puzzlement remains: why doesn’t classA3 require explicit polymorphism any more?
Summary of questions:
- Why is it possible to use
new classA2without specifying a type for'aeven thoughclassA2is declared with a type variable'a? - Why does
classA3accept a type constraint(b:classB)and does not require a bound type variable any more? - Is the functionality of
classA2andclassA3different in some subtle way, and if yes, how?
This is going to be a bit complex, so hold tight. First, let me add a
classA4variant, which happens to be what you actually need.Classes
classA2,classA3andclassA4are all subtly different, and the difference lies in how OCaml treats type polymorphism and object polymorphism. Let’s assume that two classes,b1andb2, implement theclassBtype.In terms of object polymorphism, this means that an expression of type
b1can be coerced to typeclassBusing the coercion syntax(new b1 :> classB). This type coercion discards type information (you no longer know the object is of typeb1), so it must be made explicit.In terms of type polymorphism, this means that type
b1can be used in place of any type variable that has constraint#classB(or< g : int ; h : int ; .. >). This does not discard any type information (as the type variable is replaced by the actual type), so it is performed by the type inference algorithm.Method
fofclassA3expects a parameter of typeclassB, which means a type coercion is mandatory:This also means that (as long as you coerce), any class implementing
classBcan be used.Method
fofclassA2expects a parameter of a type that matches constraint#classB, but OCaml mandates that such a type should not be unbound, so it is bound at the class level. This means that every instance ofclassA2will accept parameters of a single arbitrary type that implementsclassB(and that type will be type-inferred).It is important to note that
classA3is equivalent toclassB classA2, which is why it does not require a bound type variable, and also why it is strictly less expressive thanclassA2.Method
fofclassA4has been given an explicit type using the'a.syntax, which binds the type variable at the method level instead of the class level. It’s actually an universal quantifier, which means « this method can be called for any type'awhich implements#classB» :