当前位置 : 主页 > 手机开发 > 其它 >

OCaml模块如何导出依赖模块中定义的字段?

来源:互联网 收集:自由互联 发布时间:2021-06-22
我有一个分解,模块A定义了一个结构类型,并导出了这个类型的字段,它被定义为模块B中的一个值: a.ml: type t = { x : int}let b = B.a b.ml: open A (* to avoid fully qualifying fields of a *)let a : t = {
我有一个分解,模块A定义了一个结构类型,并导出了这个类型的字段,它被定义为模块B中的一个值:

a.ml:

type t = {
  x : int
}

let b = B.a

b.ml:

open A (* to avoid fully qualifying fields of a *)
let a : t = {
  x = 1;
}

避免循环依赖,因为B仅依赖于A中的类型声明(而不是值).

a.mli:

type t = {
  x : int
}

val b : t

据我所知,这应该是犹太教.但编译器出错了:

File "a.ml", line 1, characters 0-1:
Error: The implementation a.ml does not match the interface a.cmi:
       Values do not match: val b : A.t is not included in val b : t

当然,这一点都特别迟钝,因为不清楚哪个val b被解释为具有类型t并且具有类型A.t(并且A – 接口定义或模块定义 – 这指的是).

我假设有一些神秘的规则(沿着“结构字段必须由模块未打开时完全模块限定的名称引用”的语义,在某些时候咬每个OCaml新手),但我到目前为止不知所措.

显微镜中的模块比它看起来更微妙

(如果你的眼睛在某一点上釉,请跳到第二部分.)

让我们看看如果将所有内容放在同一个文件中会发生什么.这应该是可能的,因为单独的计算单元不会增加类型系统的功率. (注意:对于此文件以及文件a.*和b.*的任何测试使用单独的目录,否则编译器将看到可能令人困惑的编译单元A和B.)

module A = (struct
    type t = { x : int }
    let b = B.a
  end : sig
    type t = { x : int }
    val b : t
  end)
module B = (struct
    let a : A.t = { A.x = 1 }
  end : sig
    val a : A.t
  end)

哦,好吧,这不行.很明显,这里没有定义B.我们需要更精确地讨论依赖链:首先定义A的接口,然后定义B的接口,然后定义B和A的接口.

module type Asig = sig
    type t = { x : int }
    type u = int
    val b : t
  end
module B = (struct
    let a : Asig.t = { Asig.x = 1 }
  end : sig
    val a : Asig.t
  end)
module A = (struct
    type t = { x : int }
    let b = B.a
  end : Asig)

好吧,不.

File "d.ml", line 7, characters 12-18:
Error: Unbound type constructor Asig.t

你看,Asig是签名.签名是模块的规范,不再是; Ocaml中没有签名的微积分.您不能引用签名字段.您只能引用模块的字段.当你写A.t时,这指的是模块A的名为t的类型字段.

在Ocaml中,这种微妙的发生是相当罕见的.但是你试着在语言的一角捅,这就是潜伏在那里的东西.

那么当有两个编译单元时会发生什么?更接近的模型是将A视为一个以模块B为参数的仿函数. B所需的签名是接口文件b.mli中描述的签名.类似地,B是一个函数,它采用模块A,其签名在.mli中作为参数给出.哦,等等,它有点涉及:A出现在B的签名中,因此B的界面实际上定义了一个带有A并产生B的仿函数,可以这么说.

module type Asig = sig
    type t = { x : int }
    type u = int
    val b : t
  end
module type Bsig = functor(A : Asig) -> sig
    val a : A.t
  end
module B = (functor(A : Asig) -> (struct
    let a : A.t = { A.x = 1 }
  end) : Bsig)
module A = functor(B : Bsig) -> (struct
    type t = { x : int }
    let b = B.a
  end : Asig)

在这里,当定义A时,我们遇到了一个问题:我们还没有A,作为参数传递给B.(当然,除非是递归模块,但在这里我们试图了解为什么我们可以’没有他们就过去了.)

定义生成类型是副作用

根本的关键点是类型t = {x:int}是一个生成型定义.如果此片段在程序中出现两次,则定义两种不同的类型. (Ocaml采取步骤并禁止您在同一模块中定义两个具有相同名称的类型,但在顶层除外.)

实际上,正如我们在上面看到的,在模块实现中键入t = {x:int}是生成类型定义.它的意思是“定义一个名为d的新类型,它是带有字段的记录类型……”.相同的语法可以出现在模块接口中,但它有不同的含义:那里,它意味着“模块定义了类型t,它是一种记录类型……”.

由于定义生成类型两次会产生两种不同的类型,因此A定义的特定生成类型无法通过模块A(其签名)的规范完全描述.因此,使用这种生成类型的程序的任何部分实际上都是使用A的实现,而不仅仅是它的规范.

当你了解它时,定义一个生成类型,它是一种副作用.这种副作用发生在编译时或程序初始化时(这两者之间的区别仅在你开始查看仿函数时出现,我不会在这里做.)因此,重要的是要记录这种副作用何时发生:它在定义(编译或加载)模块A时发生.

因此,为了更具体地表达这一点:模块A中的类型定义类型t = {x:int}被编译为“let t be type#1729,一个新类型,它是一个带有字段的记录类型……”. (新类型意味着与以前定义的任何类型不同的类型.). B的定义定义了一个类型#1729.

由于模块B依赖于模块A,因此必须在B之前加载A.但是A的实现明显使用B的实现.这两者是相互递归的. Ocaml的错误信息有点令人困惑,但你确实超越了语言的界限.

网友评论