Когда в OCaml следует использовать расширяемые типы вариантов? - PullRequest
0 голосов
/ 17 февраля 2019

Я прошел курс обучения по OCaml до того, как были введены расширяемые варианты типов , и я не знаю о них много.У меня есть несколько вопросов:

  1. (Этот вопрос был удален, потому что он вызвал «объективное несоответствие» закрытому голосованию.)
  2. Каковы низкоуровневые последствия использования EVT, такие каккак производительность, представление памяти и (не) маршалинг?

Обратите внимание, что мой вопрос касается конкретно расширяемого варианта типа, в отличие от вопроса, предложенного как идентичный этому (чтовопрос был задан до введения ЭВЦ!).

1 Ответ

0 голосов
/ 25 февраля 2019

Расширяемые варианты весьма отличаются от стандартных вариантов с точки зрения поведения во время выполнения.

В частности, конструкторы расширения представляют собой значения времени выполнения, которые живут внутри модуля, в котором они были определены.Например, в

 type t = ..
 module M = struct
   type t +=A
 end
 open M

вторая строка определяет новое значение конструктора расширения A и добавляет его к существующим конструкторам расширения M во время выполнения.Напротив, классические варианты на самом деле не существуют во время выполнения.

Можно наблюдать это различие, заметив, что я могу использовать модуль компиляции только для mli для классических вариантов:

 (* classical.mli *)
 type t = A

 (* main.ml *)
 let x = Classical.A

изатем скомпилируйте main.ml с

ocamlopt classic.mli main.ml

без проблем, так как в модуле Classical нет значения, которое включено.

В отличие от расширяемых вариантов, это невозможно.Если у меня

 (* ext.mli *)
  type t = ..
  type t+=A

 (* main.ml *)
 let x = Ext.A

, то команда

ocamlopt ext.mli main.ml

завершится неудачно с

Ошибка: необходимый модуль Ext не доступен

, поскольку отсутствует значение времени выполнения для конструктора расширения Ext.A.

Вы также можете посмотреть как имя, так и идентификаторконструктора расширения, использующего модуль Obj для просмотра этих значений

 let a = [%extension_constructor A]
 Obj.extension_name a;;
  • : string = "MA"
 Obj.extension_id a;;
  • : int = 144

(Этот id довольно хрупкий и его значение не имеет особого значения.) Важным моментом является то, что конструктор расширений различается с помощью ихместо в памяти.Следовательно, конструкторы с n аргументами реализованы как блок с n+1 аргументами, где первый скрытый аргумент - это конструктор расширения:

type t += B of int
let x = B 0;;

Здесь x содержит два поля, а не одно:

 Obj.size (Obj.repr x);;
  • : int = 2

И первое поле - это конструктор расширения B:

 Obj.field (Obj.repr x) 0 == Obj.repr [%extension_constructor B];;
  • : bool = true

Предыдущий оператор также работает для n=0: расширяемые варианты никогда не представляются как целочисленные теги, в отличие от классических вариантов.

Поскольку сортировка не сохраняет физического равенства, это означает, что расширяемый тип суммы не может быть распределен без потери их идентичности.Например, выполнение туда и обратно с

 let round_trip (x:'a):'a = Marshall.from_string (Marshall.to_string x []) 0

, а затем проверка результата с помощью

  type t += C
  let is_c = function
  | C -> true
  | _ -> false

приводит к ошибке:

   is_c (round_trip C)
  • : bool = false

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

Это также означает, что сопоставление с образцом для расширяемого типа во время выполнения сильно отличается.Например, если я определю простой вариант

 type s = A of int | B of int

и определю функцию f как

let f = function
| A n | B n -> n

, то компилятор достаточно умен, чтобы оптимизировать эту функцию для простого доступа к первомуПоле аргумента.

Вы можете проверить с помощью ocamlc -dlambda, что указанная выше функция представлена ​​в промежуточном представлении Lambda следующим образом:

(function param/1008 (field 0 param/1008)))

Однако с расширяемыми вариантами не только нам нужнышаблон по умолчанию

   type e = ..
   type e += A of n | B of n
   let g = function
   | A n | B n -> n
   | _ -> 0

, но нам также нужно сравнить аргумент с каждым конструктором расширения в совпадении, ведущем к более сложному лямбда-IR для совпадения

 (function param/1009
   (catch
     (if (== (field 0 param/1009) A/1003) (exit 1 (field 1 param/1009))
       (if (== (field 0 param/1009) B/1004) (exit 1 (field 1 param/1009))
         0))
    with (1 n/1007) n/1007)))

Наконец, чтобы завершитьна реальном примере расширяемых вариантов в OCaml 4.08 модуль Format заменил свои определяемые пользователем теги на основе строк расширяемыми вариантами.

Это означает, что определение новых тегов выглядит следующим образом:

Сначала мы начнем с фактического определения новых тегов

 type t =  Format.stag = ..
 type Format.stag += Warning | Error

Затем функции перевода для этих новых тегов

let mark_open_stag tag =
match tag with
| Error -> "\x1b[31m" (* aka print the content of the tag in red *)
| Warning -> "\x1b[35m" (* ... in purple *)
| _ -> ""

let mark_close_stag _tag =
  "\x1b[0m" (*reset *)

Установка нового тега выполняется с помощью

 let enable ppf =
    Format.pp_set_tags ppf true;
    Format.pp_set_mark_tags ppf true;
    Format.pp_set_formatter_stag_functions ppf
    { (Format.pp_get_formatter_stag_functions ppf ()) with
    mark_open_stag; mark_close_stag }

С помощью некоторой вспомогательной функции печать с использованием этих новых тегов может быть выполнена с помощью

 Format.printf "This message is %a.@." error "important"
 Format.printf "This one %a.@." warning "not so much"

По сравнению со строковыми тегами есть несколько преимуществ:

  • меньше места для написанияошибка
  • нет необходимости сериализации / десериализации потенциально сложных данных
  • нет путаницы между разными конструкторами расширений с одинаковыми именами.
  • объединение в цепочку нескольких пользовательских mark_open_stag функцийтаким образом, безопасно: каждая функция может распознавать только свои собственные конструкторы расширений.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...