gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-05 00:02:09 25 4
gpt4 key购买 nike

我有一个分解模块 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--接口(interface)定义或模块定义--this 指的是哪个)。

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

最佳答案

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

(如果您的眼睛在某个时候发呆,请跳到第二部分。)

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

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的接口(interface)先是 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 ,这是指名为 t 的类型字段模块 A .

在 Ocaml 中,这种微妙之处很少出现。但是你试图戳一下语言的一个角落,这就是潜伏在那里的东西。

那么当有两个编译单元时会发生什么?更接近的模型是看 A作为采用模块的仿函数 B作为论据。 B 所需的签名是接口(interface)文件 b.mli中描述的那个.同样, B是一个函数,它接受一个模块 A其签名在 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 . (当然,除非递归模块,但在这里我们试图了解为什么没有它们我们就无法生存。)

定义一个生成类型是一个副作用

根本的症结在于 type t = {x : int}是一个生成类型定义。如果这个片段在一个程序中出现两次,则定义了两种不同的类型。 (Ocaml 采取措施并禁止您在同一个模块中定义两个具有相同名称的类型,但在顶层除外。)

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

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

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

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

自模块 B取决于模块 A , A必须在 B 之前加载.但是执行 A显然使用了 B 的实现.两者是相互递归的。 Ocaml 的错误信息有点令人困惑,但您确实超出了语言的界限。

关于module - OCaml 模块如何导出依赖模块中定义的字段?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8382232/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com