в ответе Гаше есть хороший совет. Я собираюсь объяснить открытые и закрытые профсоюзы немного подробнее.
Во-первых, вам нужно различать два вида объединений: базовые варианты (без обратной кавычки) и полиморфные варианты (с обратной кавычкой).
- Базовые варианты являются генеративными: если вы определяете два типа с одинаковыми именами конструкторов в разных модулях
M1
и M2
, у вас есть разные типы. M1.Foo
и M2.Foo
- разные конструкторы. `Foo
- это всегда один и тот же конструктор, независимо от того, где вы его используете.
- Кроме того, полиморфные варианты могут делать то же, что и базовые варианты, и даже больше. Но с большой мощностью приходит большая сложность, поэтому вы должны использовать их только при необходимости и осторожно.
Полиморфный вариантный тип описывает, какие конструкторы у этого типа могут быть. Но многие типы полиморфных вариантов до конца не известны - они содержат переменные типа (строки). Рассмотрим пустой список []
: его тип 'a list
, и его можно использовать во многих контекстах, которые присваивают 'a
более конкретные типы. Например:
# let empty_list = [];;
val empty_list : 'a list = []
# let list_of_lists = [] :: empty_list;;
val list_of_lists : 'a list list = [[]]
# let list_of_integers = 3 :: empty_list;;
val list_of_integers : int list = [3]
То же самое и с переменными типа строки. Открытый тип, обозначенный как [> … ]
, имеет строковую переменную, которая может быть создана для охвата большего количества конструкторов каждый раз, когда используется значение.
# let foo = `Foo;;
val foo : [> `Foo ] = `Foo
# let bar = `Bar;;
val bar : [> `Bar ] = `Bar
# let foobar = [foo; bar];;
val foobar : [> `Bar | `Foo ] list = [`Foo; `Bar]
Тот факт, что конструктор присутствует в типе, не означает, что каждое использование этого типа должно разрешать все конструкторы. [> …]
говорит, что тип должен иметь по крайней мере эти конструкторы, и дважды [< …]
говорит, что тип должен иметь не более этих конструкторов. Рассмотрим эту функцию:
# let h = function `Foo -> `Bar | `Bar -> `Foo;;
val h : [< `Bar | `Foo ] -> [> `Bar | `Foo ] = <fun>
h
может обрабатывать только Foo
и Bar
, поэтому тип ввода может не допускать другие конструкторы; но нормально вызывать h
для типа, который разрешает только Foo
. И наоборот, h
может возвращать Foo
или Bar
, и любой контекст, в котором используется h
, должен разрешать как Foo
, так и Bar
(и может разрешать другие).
Закрытые типы возникают, когда для типа совпадают минимальные и максимальные требования конструктора. Например, добавим ограничение, согласно которому h
должен иметь одинаковый тип ввода и вывода:
# let hh : 'a -> 'a = function `Foo -> `Bar | `Bar -> `Foo;;
val hh : [ `Bar | `Foo ] -> [ `Bar | `Foo ] = <fun>
Закрытые типы редко возникают естественным образом в результате вывода типов. В большинстве случаев, как здесь, они являются следствием аннотации пользователя. Когда вы используете полиморфные аннотации, рекомендуется определять именованные типы и использовать их по крайней мере для каждой функции верхнего уровня. В противном случае предполагаемый тип, вероятно, будет немного более общим, чем вы думали. Хотя это редко открывает путь к ошибкам, это часто означает, что любая ошибка типа будет диагностирована позже, чем могла бы, и будет генерировать очень длинное сообщение об ошибке, в котором будет трудно найти полезные биты.
Я рекомендую прочитать и проработать (т.е. перепечатать примеры на верхнем уровне, немного поэкспериментировать, чтобы убедиться, что вы понимаете каждый шаг) руководство по полиморфному варианту в руководстве Ocaml.
person
Gilles 'SO- stop being evil'
schedule
27.02.2011