12.5 Модули первого класса
(Введены в OCaml 3.12; синтаксис шаблонов и вывод типов пакетов введены в 4.00; структурное сравнение типов пакетов введено в 4.02.; меньше скобок требуется начиная с 4.05)
|
Модули обычно рассматриваются как статические компоненты. Это расширение позволяет упаковать модуль в качестве значения первого класса, которое позже можно динамически распаковать в модуль.
Выражение ( module module-expr : package-type ) преобразует модуль (структура или функтор), обозначенный выражением модуля module-expr, в значение языка ядра, которое инкапсулирует этот модуль. Тип этого значения языка ядра — ( module package-type ). Аннотация package-type может быть опущена, если её можно вывести из контекста.
Обратно, выражение модуля ( val expr : package-type ) вычисляет выражение языка ядра expr до значения, которое должно иметь тип module package-type, и извлекает модуль, который был инкапсулирован в этом значении. Опять же package-type можно опустить, если тип expr известен. Если выражение модуля уже заключено в скобки, как аргументы функторов, дополнительные скобки не нужны: Map.Make(val key).
Шаблон ( module module-name : package-type ) сопоставляет пакет с типом package-type и привязывает его к module-name. Он недоступен в глобальных операциях привязки let. Опять же package-type можно опустить, если его можно вывести из окружающего шаблона.
Синтаксический класс package-type, встречающийся в выражении типа ( module package-type ) и в аннотированных формах, представляет собой подмножество типов модулей. Это подмножество состоит из именованных типов модулей с необязательными ограничениями ограниченной формы: могут быть указаны только непараметризованные типы.
Для целей проверки типов (и начиная с OCaml 4.02) типы пакетов сравниваются с использованием структурного сравнения типов модулей.
В общем случае выражение модуля ( val expr : package-type ) не может использоваться в теле функтора, так как это может вызвать некорректность в сочетании с прикладными функторами. Начиная с OCaml 4.02, это ограничение ослаблено в двух аспектах: если package-type не содержит именованных объявлений типов (т.е. типов, созданных с помощью собственного идентификатора), то это выражение может быть использовано где угодно, и даже если оно содержит такие типы, оно может использоваться внутри тела генеративного функтора, описанного в разделе 12.15. Его также можно использовать где угодно в контексте локальной привязки модуля let module module-name = ( val expr1 : package-type ) in expr2.
Базовый пример
Типичное использование модулей первого класса — это выбор в момент выполнения среди нескольких реализаций подписи. Каждая реализация — это структура, которую мы можем инкапсулировать как модуль первого класса, а затем хранить в структуре данных, такой как хеш-таблица:
type picture = … module type DEVICE = sig val draw : picture -> unit … end let devices : (string, (module DEVICE)) Hashtbl.t = Hashtbl.create 17 module SVG = struct … end let _ = Hashtbl.add devices "SVG" (module SVG : DEVICE) module PDF = struct … end let _ = Hashtbl.add devices "PDF" (module PDF : DEVICE)
Затем мы можем выбрать одну реализацию, например, на основе аргументов командной строки:
let parse_cmdline () = …
module Device =
(val (let device_name = parse_cmdline () in
try Hashtbl.find devices device_name
with Not_found ->
Printf.eprintf "Unknown device %s\n" device_name;
exit 2)
: DEVICE)
Или выбор можно выполнить внутри функции:
let draw_using_device device_name picture =
let module Device =
(val (Hashtbl.find devices device_name) : DEVICE)
in
Device.draw picture
Расширенные примеры
С модулями первого класса можно параметризовать некоторый код по реализации модуля без использования функтора.
let sort (type s) (module Set : Set.S with type elt = s) l = Set.elements (List.fold_right Set.add l Set.empty) val sort : (module Set.S with type elt = 's) -> 's list -> 's list =
Чтобы использовать эту функцию, можно обернуть функтор Set.Make:
let make_set (type s) cmp =
let module S = Set.Make(struct
type t = s
let compare = cmp
end) in
(module S : Set.S with type elt = s)
val make_set : ('s -> 's -> int) -> (module Set.S with type elt = 's) =
© 1995-2022 INRIA.
https://v2.ocaml.org/releases/5.0/htmlman/firstclassmodules.html