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 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 sub
where super
is 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;
精彩评论