Варианты или полиморфные варианты?
Я заметил, что среди известных мне программистов на OCaml некоторые из них всегда используют полиморфные варианты (варианты, которые не объявлены, с префиксом с обратной кавычкой), в то время как другие никогда не используют полиморфные варианты и предпочитают варианты, объявленные в типах.
За исключением соображений производительности (полиморфные варианты в настоящее время компилируются менее эффективно, чем простые варианты), как опытные разработчики OCaml выбирают между ними?
3 ответа
Мое использование может быть разделено на следующие 5 категорий. 1. интерфейс 2. модульность 3. разборчивость 4. краткость 5. хитрости
- Если тип варианта является внутренним только для модуля, я использую обычные варианты, потому что, как вы сказали, они компилируются более эффективно.
- Если тип варианта экспортируется в интерфейсе, и я чувствую, что некоторые случаи могут появиться в других модулях, но не обязательно иметь смысл делать их зависимыми от модуля, я использую полиморфные варианты, потому что они не привязаны к системе пространства имен модуля., Примеры: тип кодировки типа
Xmlm
, Кроме того, наличие типа сигнала в качестве типа варианта означает, что вы можете разрабатывать модули, используя ту же идею для обработки XML, не вводя зависимости отXmlm
, - Если тип варианта экспортируется в интерфейсе, я нахожу, что иногда слишком многословно использовать обычные варианты, когда значения типа варианта передаются функциям модуля. Пример: тип версии
Uuidm
, Вместо того, чтобы писатьUuidm.create Uuidm.V4
ты можешь просто написатьUuidm.create `V4
, что так же ясно и менее многословно. - Иногда конкретная функция может возвращать разные случаи. Если эти случаи используются только этой функцией, я объявляю тип функции в интерфейсе, не вводя определение типа. Например
parse : string -> [`Error of string | `Ok of t]
- Полиморфные варианты и их подтипы позволяют применять статические инварианты с фантомными типами. Кроме того, возможность их постепенного определения может быть полезна как для статического применения инвариантов, так и для целей документирования.
Наконец, я иногда использую полиморфные варианты в реализации модуля в соответствии с 4., но без их отображения в интерфейсе. Я не одобряю это использование, если вы не объявите полиморфные варианты и не закроете их, потому что это ослабляет дисциплину статической типизации.
Единственная причина, по которой я использую полиморфные варианты в большинстве интерфейсных модулей, заключается в том, чтобы обойти проблемы именования классических вариантов.
Если бы следующее могло работать, полиморфные варианты больше не были бы полезны в большинстве случаев:
тип t1 = строка строки | Int of int | Bool of bool | Список списка t1 тип t2 = строка строки | Int of int | Другой позвольте упростить х = сопоставить (x: t1) с Строка s -> Строка s | Int n -> Int n | Bool _ | Список _ -> Другое
2014-02-21 Обновление: приведенный выше код теперь действителен в OCaml 4.01. Ура!
Это неправда, что полиморфные варианты всегда менее эффективны. Используя пример Мартина:
type base = [`String of string | `Int of int]
type t1 = [base | `Bool of bool | `List of t1 list]
type t2 = [base | `Other]
let simplify (x:t1):t2 = match x with
| #base as b -> b
| `Bool _ | `List _ -> `Other
Чтобы сделать это со стандартными вариантами, требуется два разных типа и полное перекодирование, при полиморфных вариантах базовый случай является физически инвариантным. Эта функция действительно вступает в свои права при использовании открытой рекурсии для переписывания терминов:
type leaf = [`String of string | `Int of int]
type 'b base = [leaf | `List of 'b list]
type t1 = [t1 base | `Bool of bool ]
type t2 = [t2 base | `Other]
let rec simplify (x:t1):t2 = match x with
| #leaf as x -> x
| `List t -> `List (List.map simplify t)
| `Bool _ -> `Other
и преимущества еще больше, когда функции перезаписи также учитывают открытую рекурсию.
К сожалению, вывод типа Хиндли-Милнера Окамла недостаточно силен, чтобы делать подобные вещи без явной типизации, что требует тщательной факторизации типов, что, в свою очередь, затрудняет прототипирование. Кроме того, иногда требуются явные принуждения.
Большим недостатком этого метода является то, что для терминов с несколькими параметрами вскоре возникает довольно запутанный комбинаторный взрыв типов, и в итоге легче отказаться от статического применения и использовать тип кухонной раковины с подстановочными знаками и исключениями (т.е. динамическая типизация).