Типы открытых и закрытых профсоюзов в Ocaml

Я впервые изучаю OCaml, имея некоторый опыт работы с F # и Haskell. По сути, многое выглядит знакомо, но одна вещь не является концепцией «открытых» и «закрытых» объединений (с обратным апострофом и синтаксисом [‹).

Для чего они нужны и как часто они используются?


person J Cooper    schedule 27.02.2011    source источник
comment
Подробнее о том, почему вы должны использовать полиморфные варианты, см. Также ответы здесь: stackoverflow.com/questions/1746743/   -  person aneccodeal    schedule 27.02.2011


Ответы (2)


в ответе Гаше есть хороший совет. Я собираюсь объяснить открытые и закрытые профсоюзы немного подробнее.

Во-первых, вам нужно различать два вида объединений: базовые варианты (без обратной кавычки) и полиморфные варианты (с обратной кавычкой).

  • Базовые варианты являются генеративными: если вы определяете два типа с одинаковыми именами конструкторов в разных модулях 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
comment
Мне нужно больше подумать по этому поводу, поскольку он кажется довольно продвинутым, но спасибо за подробный ответ. - person J Cooper; 27.02.2011

Вам необходимо прочитать «Повторное использование кода с полиморфными вариантами» Жака Гаррига:

http://www.math.nagoya-u.ac.jp/~garrigue/papers/fose2000.html

Проблема с полиморфными вариантами заключается в том, что они настолько гибки, что вывод типа не может сильно помочь вам с ошибками в коде полиморфных вариантов. Например, если вы неправильно введете имя одного конструктора, компилятор не сможет пометить ошибку, он только выведет несколько другие типы с обычными конструкторами, а также с ошибкой. Ошибки будут обнаружены позже, когда вы попытаетесь объединить ошибочный код с функцией со строгими предположениями о вариантах (закрытое сопоставление с образцом) с громоздким сообщением об ошибке.

Мой совет пользователям полиморфных вариантов - массово использовать аннотации для управления проверкой типов: каждый раз, когда вы берете полиморфный вариант в качестве входных или выходных, функция должна быть аннотирована точным типом для вариантной части. Это защитит вас от большинства проблем с логическим выводом и заставит создать выразительный набор определений типов, которые можно составить и помочь в рассуждении о вашей программе.

person gasche    schedule 27.02.2011