1

我们可以将模块打包为 value 并将其解包回模块(模块作为一等公民)。此外,我们可以将模块类型打包到类型,但是......是否可以从类型中解压缩模块类型?如果是 - 如何?如果不是 - 为什么?下面的草图显示了我的意思。

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 在模块语言和核心语言之间存在边界。我问“how”是因为有希望,问“why”是因为确定这个问题是在OCaml的设计过程中打开的,所以这个边界是基于一些决定和原因。

4

1 回答 1

2

类型定义

type 'a t = (module S with type t = 'a)

并不是真正将类型模块打包S到类型,而是为类型表达式t提供更短名称的类型别名,它表示类型为多态的模块,而不是它们定义的类型。'a t(module S with type t = 'a)St

只要你有一个类型的值,'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)

有效,但显然它在语义上看起来是相等的(cf,我们也可以说Map.Make(Int).t但不能说Map.Make(Int).empty,我们必须在此之前将模块表达式绑定到模块名称)。1

从语法上讲,打包模块类型表达式应该是(module <package-type>)打包类型是标识符或具有有限数量约束的标识符。当然,这种限制的根本原因不是解析器和语法,而是类型推断的复杂性。潜在的问题真的很难解释,但它来自模块系统和函子的力量,因此如果在模块类型中允许模块表达式的全部力量,很容易引入不健全。

这并不是说存在重大的理论限制,基本上,我们正在达到原始设计的限制(这对于 1970 年创建的语言可能很好,作为证明助手的元语言,带有模块多年后添加,并且是最近才添加的一流模块)。

如果语言是从头开始设计的,那么值和模块之间的分离就不会那么粗糙,如果存在的话,例如,参见 1ML 语言。

进一步阅读


1) ) 这不是一个很大的限制,因为我们总是可以将模块类型表达式绑定到名称,即使在函数体中也是如此。

于 2020-06-10T13:58:08.883 回答