Как упомянул Реми, проблема с вашим кодом заключается в том, что система типов OCaml поддерживает только один тип на выражение: выражение типа sub
не относится к типу super
.В вашем примере myFunction
ожидает аргумент типа super
, а выражение new sub
имеет тип sub
, поэтому возникает проблема.
Обновление имеет важное значение для объектно-ориентированного программирования, и OCamlдействительно поддерживает это с двумя отличными конструкциями.
Первым является принуждение типа .Если super
является супертипом sub
(это означает, что семантически значения типа sub
ведут себя как значения super
) и x : sub
, то (x :> super) : super
.Оператор типа :>
делает преобразование явным - эквивалент того, что неявно делают популярные объектно-ориентированные языки, когда вы используете значение типа sub
, где ожидается super
.
Второй - ограничения супертипа : требование, чтобы переменная данного типа была подтипом данного типа.Это записывается как #super
или (#super as 'a)
, если вы хотите назвать переменную типа внутри.Ограничения супертипа на самом деле не изменяют тип выражения, как приведения типов, они просто проверяют, является ли тип допустимым подтипом требуемого типа.
Чтобы лучше понять разницу, рассмотрим следующий пример:
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
Тип pic_smallest_coerce
равен with_size -> with_size -> with_size
: даже если вы передали два экземпляра person
,возвращаемое значение будет иметь тип with_size
, и вы не сможете вызвать его name
метод.
Тип pic_smallest_subtype
- (#with_size as 'a) -> 'a -> 'a
: если вы передадите два экземпляра person
, система типов определит это 'a = person
и правильно определит возвращаемое значение как тип person
(которыйпозволяет использовать метод name
).
Короче говоря, ограничения супертипа просто гарантируют, что ваш код будет работать, без потери какой-либо информации о типе - переменная сохраняет свой первоначальный тип,Принуждение к типу фактически теряет информацию о типе (что при отсутствии приведения является очень неприятной вещью), поэтому ее следует использовать только в качестве крайней меры в двух ситуациях:
1. Вы не можете иметь полиморфную функцию.Ограничения супертипа основаны на том, что #super
является переменной свободного типа, поэтому, если вы не можете позволить себе иметь переменную свободного типа в вашем коде, вам придется обходиться без нее.
2. Вам необходимо хранить значения различных фактических типов в одном контейнере.Список или ссылка, которые могут содержать либо person
, либо box
экземпляров, будут использовать with_size
и приведение:
let things = [ my_person :> with_size ; my_box :> with_size ]
Обратите внимание, что алгоритм вывода типов самостоятельно обнаружит ограничения супертипа (он будетне определять, какой класс или тип класса вы намеревались использовать, но он будет создавать буквальный тип класса):
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
Таким образом, за редкими исключениями, аннотирование фактических ограничений супертипа является полезным упражнением только при документированиитвой код.