Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the proper way to make an Ocaml subclass with additional methods?

In Ocaml I'm struggling with subclassing and types:

class super =  
  object (self)  
  method doIt =   
    ...  
end;  

class sub =  
  object (self)  
  inherit super  
  method doIt =   
    ...  
    self#somethingElse  
    ...  

  method somethingElse =  
    ...  
end;  

let myFunction (s:super) =  
  ...  

myFunction new sub

Apparently in Ocaml, class sub is not a "subtype" of class super, because the sub#doIt method calls a method in sub that is not present in super. However, this seems like a pretty common use-case for OO programming. What is the recommended way to accomplish this?

like image 462
Dave Rafkind Avatar asked Feb 06 '11 17:02

Dave Rafkind


2 Answers

As mentioned by Rémi, the problem with your code is that the OCaml type system supports only one type per expression: an expression of type sub is not of type super. In your example, myFunction expects an argument of type super, and the expression new sub is of type sub, hence the issue.

Upcasting is essential to object-oriented programming, and OCaml does support it with two distinct constructs.

The first is type coercion. If super is a supertype of sub (meaning that semantically, values of type sub behave as values of super), and x : sub, then (x :> super) : super. The :> type operator makes the conversion explicit — the equivalent of what popular object-oriented languages do implicitly when you use avalue of type subwhere superis expected.

The second is supertype constraints: requiring that a given type variable is a subtype of a given type. This is written as #super or (#super as 'a) if you wish to name the type variable within. Supertype constraints don't actually change the type of the expression like type coercion does, they merely check that the type is a valid subtype of the required type.

To become more aware of the difference, consider the following example:

class with_size ~size = object 
  val size    = size : int
  method size = size
end

class person ~name ~size = object
  inherit with_size ~size
  val name    = name : string
  method name = name
end

let pick_smallest_coerce (a : with_size) (b : with_size) = 
  if a # size < b # size then a else b

let pick_smallest_subtype (a : #with_size) (b : #with_size) = 
  if a # size < b # size then a else b

The type of pic_smallest_coerce is with_size -> with_size -> with_size: even if you passed two person instances, the return value would be of type with_size and you would not be able to call its name method.

The type of pic_smallest_subtype is (#with_size as 'a) -> 'a -> 'a: if you pass two person instances, the type system would determine that 'a = person and correctly identify the return value as being of type person (which lets you use the name method).

In short, supertype constraints merely make sure that your code will run, without losing any type information at all — the variable retains its original type. Type coercion actually loses type information (which, in the absence of down-casting, is a very nasty thing), so it should only be used as a last resort in two situations:

1. You cannot have a polymorphic function. Supertype constraints rely on #super being a free type variable, so if you cannot afford to have a free type variable in your code, you will have to do without it.

2. You need to actually store values of different actual types in the same container. A list or reference that can contain either person or box instances will use with_size and coercion:

let things = [ my_person :> with_size ; my_box :> with_size ]

Do note that the type inference algorithm will discover supertype constraints on its own (it will not determine what class or class type you intended to use, but it will construct a literal class type):

let pick_smallest_infer a b = 
  if a # size < b # size then a else b

val pick_smallest_infer : (< size : 'a ; .. > as 'b) -> 'b -> 'b

As such, with rare exceptions, annotating the actual supertype constraints is an useful exercise only when documenting your code.

like image 95
Victor Nicollet Avatar answered Nov 09 '22 04:11

Victor Nicollet


sub is probably a subtype of super. But in ocaml there is no implicit type conversion. So your function don't accept subtype of super. You have to explicitly make a coercion :

let myFunction (s:super) =  
  ...  

myFunction (new sub :> super)

Or preferably accept subtype of super:

let myFunction (s:#super) =  
  ...  

myFunction new sub
like image 8
Rémi Avatar answered Nov 09 '22 04:11

Rémi