Как распаковать "тип модуля", упакованный в "тип", обратно в "тип модуля"?
Мы можем упаковать модуль в ценность и распаковать его обратно в модуль (модули как первоклассные граждане). Также, мы можем упаковать тип модуля в тип, но... Можно ли распаковать тип модуля из типа? Если это так - как? Если нет - почему? Следующий рисунок показывает, что я имею в виду.
module type S = sig
type t
end
type 'a t = (module S with type t = 'a)
module type S' = sig
include SIG_FROM ( int t )
end
или
module type S'' = SIG_FROM ( int t )
Добавлено примечание:
Так почему я спрашиваю об этом. Очень часто Ocaml не может определить тип экземпляра первоклассного модуля, и он должен быть аннотирован. Сделать это можно двумя способами:
1) подписью
module type INTERNAL_S =
EXTERNAL_S with type a = char
and type b = int
and type c = string
let f (module M : INTERNAL_S) a b c =
M.f a b (c * 2)
2) по типу
type e =
( module EXTERNAL_S
with type a = char
and type b = int
and type c = string
)
let f ((module M) : e) a b =
M.f a (b * 2)
Как правило, второй способ короче и удобен для чтения, особенно в подписях (.mli).
val g : (module INTERNAL_S) -> (module INTERNAL_S) ->
char -> int -> string
val g : e -> e -> char -> int -> string
И мы создаем типы из типов модулей, чтобы упростить чтение кода или в случае, когда это необходимо (например, когда этого ожидает функтор). И иногда мне нужен только тип, но я должен также объявить тип модуля, потому что поддержка ограничения новых типов из типов модулей ограничена
и ограничение новых типов (привязанных к типу модуля) от типов (привязанных к типам модуля) отсутствует.
(* this kind of constructing is possible *)
let x : 'a t = (module struct
include A
include B
include (val C.some_value)
let new_f o = o
end)
(* but this isn't *)
type 'a t = (module sig
include A with type t = t
include B with type t := t
include SIG_FROM ( (int, int) sig_type )
val new_f : t -> t
end)
На мой взгляд, такой путь назад делает модули более первоклассными. Кроме того, он симметричен отношениям между экземплярами и модулями (как я понимаюlet x = (module X)
а также let module X = (val x)
тоже привязки). Симметрия - это хорошо (например, частично существует между функцией и функторами). Но, как я вижу здесь, у OCaml есть граница между языком модуля и основным языком. Я спросил "как", потому что есть надежда, и спрашиваю "почему", потому что уверен, что этот вопрос был открыт в процессе проектирования OCaml, поэтому эта граница основана на некоторых решениях и причинах.
1 ответ
Определение типа
type 'a t = (module S with type t = 'a)
на самом деле не упаковка модуля типаS
печатать t
но псевдоним типа, который дает более короткое имя, 'a t
к выражению типа (module S with type t = 'a)
, что обозначает модули типа S
полиморфный по типу t
что они определяют.
Где угодно, где есть значение типа 'a t
пока 'a t
не абстрагируется и известно, что он равен (module S with type t = 'a)
вы можете распаковать это значение и даже использовать его как параметр функтора. Вы даже можете восстановить тип упакованного модуля, используяmodule type of
построить, например,
let apply : type a. a t -> unit = fun (module S) ->
let module X = struct
module type S' = sig
include module type of S
end
end in ()
В качестве примечания вы также можете упаковать модули, определяющие подписи, например,
module type Algebra = sig
module type S = sig
type t
val add : t -> t -> t
val sub : t -> t -> t
end
end
type signature = (module Algebra)
Тогда мы можем определить SIGFROM
в качестве
module SIGFROM(A : Algebra) = struct
module type S = A.S
end
и распаковать подпись из запакованного модуля
let example : signature -> unit = fun s ->
let module A = SIGFROM(val s) in
let module B = struct
module type S = A.S
type t = (module S)
type t0 = (module A.S)
type 'a t1 = (module A.S with type t = 'a)
end in
()
Однако мы не можем писать напрямую,
type t = (module SIGFROM(val s))
или даже создать тип модуля упакованного модуля из существующих и определенных в сигнатурах верхнего уровня, например,
type t = (module sig include module type of Int end)
не работает, но
module type I = sig include module type of Int end
type t = (module I)
работает, хотя, очевидно, он выглядит семантически равным (ср, мы также можем сказать Map.Make(Int).t
но не могу сказать Map.Make(Int).empty
, перед этим мы должны привязать выражение модуля к имени модуля).1
Синтаксически выражение типа упакованного модуля должно быть (module <package-type>)
где упакованный тип - это либо идентификатор, либо идентификатор с ограниченным числом ограничений. Конечно, основная причина этого ограничения не в синтаксическом анализаторе и синтаксисе, а в сложности вывода типов. Основную проблему действительно трудно объяснить, но она проистекает из мощности модульной системы и функторов, так что очень легко внести несостоятельность, если полная мощность выражений модуля будет разрешена в типах модулей.
Это не означает, что существует значительное теоретическое ограничение, в основном, мы достигаем предела оригинального дизайна (что, вероятно, подходит для языка, который был создан в 1970 году в качестве метаязыка для помощника по доказательству с модулями добавлены много лет спустя, а первоклассные модули - сравнительно недавно).
Если бы язык разрабатывался с нуля, то разделение между значениями и модулями было бы менее грубым, если оно вообще существует, например, см. Язык 1ML.
Дополнительная литература
- 1ML - Core and Modules United(Первоклассные модули F-ing)
- F-ing модули
- Первоклассные модули: скрытая сила и соблазнительные обещания
1)) Что не является большим ограничением, поскольку мы всегда можем привязать выражение типа модуля к имени, даже в теле функции.