开发者

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

开发者 https://www.devze.com 2023-02-09 02:07 出处:网络
In Ocaml I\'m struggling with subclassing and types: class super = object (self) method doIt = ... end; class sub =

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 pre开发者_如何学编程tty common use-case for OO programming. What is the recommended way to accomplish this?


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.


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


If you want myFunction to accept any subtype of super as an argument, then you should define it like this:

let myFunction s =
  let s = (s :> super) in
  ...

...or equivalently...

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

As for your comment that sub appears not to be a subtype of super in your example because the doIt method in sub is what gets dispatched when the object value to the left of the # operator is of class type sub, well, I think you're mistaken. That's precisely what you should expect.

To allow methods in the sub class to invoke the doIt method in the super class, you should define them like so:

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

class sub = object (self)  
  inherit super as parent

  method doIt =   
    ...
    let _ = parent#doIt in
    ...
    self#somethingElse  
    ...  

  method somethingElse =  
    ...  
end;
0

精彩评论

暂无评论...
验证码 换一张
取 消