Как распаковать "тип модуля", упакованный в "тип", обратно в "тип модуля"?

Мы можем упаковать модуль в ценность и распаковать его обратно в модуль (модули как первоклассные граждане). Также, мы можем упаковать тип модуля в тип, но... Можно ли распаковать тип модуля из типа? Если это так - как? Если нет - почему? Следующий рисунок показывает, что я имею в виду.

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.

Дополнительная литература


1)) Что не является большим ограничением, поскольку мы всегда можем привязать выражение типа модуля к имени, даже в теле функции.

Другие вопросы по тегам