单个 OCaml 模块导致对接口的假设不一致

Single OCaml module causes inconsistent assumptions about interface

这是我基于 ocamlbuild 的项目结构:

_tags.ml:

true: package(batteries)

Main.mlpack

Stream

Main/Stream.ml

module MyStream = BatStream

我正在尝试使用

编译Main模块
ocamlbuild -use-ocamlfind Main.cmo

错误信息对我来说似乎很不合逻辑:

+ ocamlfind ocamlc -pack Main/Stream.cmo -o Main.cmo
File "_none_", line 1:
Error: The files Main/Stream.cmi and Main/Stream.cmi
       make inconsistent assumptions over interface Stream
Command exited with code 2.
Compilation unsuccessful after building 3 targets (0 cached) in 00:00:00

这是使用来自 OPAM 的 OCaml 4.02.1。

只有在连接电池时才会出现这种情况,所以我只能认为Batteries.StreamMain.Stream之间存在冲突。事实上,如果我添加更多具有依赖关系的模块,我会收到类似

的消息
Error: The files /home/ken/.opam/4.02.1/lib/batteries/batteries.cmi
       and Main/Stream.cmi make inconsistent assumptions
       over interface Stream

但是,我不希望子模块发生冲突。

为什么会这样?在我看来,模块可能通过接口与自身发生冲突是不可能的。

BatStream 扩展了 stdlib Stream 模块。冲突可能发生在您的本地 Stream 模块和标准库 Stream 模块之间。

OCaml 有一个用于编译单元名称的平面命名空间。当编译单元使用某个模块时,它会记录模块接口的名称和摘要(基本上是接口的 CRC)。一致性检查确保具有相同名称的两个接口具有相同的摘要(基本上表示相同的实现)。尽管错误消息确实具有误导性,但它仍然是正确的(尽管措辞可能会好得多)。让我们使用 ocamlobjinfo 工具:

$ ocamlobjinfo _build/Main/Stream.cmi
File _build/Main/Stream.cmi
Unit name: Stream
Interfaces imported:
        83d31bf1e61f22b62a8b2728a55f2593        Stream
        d0b21ad0c1f4e93fa8c05b9ded519b52        Stream
        999b28e3b7638771c87eebf5a8325e42        Pervasives
        60c2e7663dd57d13b5920931742e1c10        Format
        9642e3ed163e46770985ca668738ed5f        CamlinternalFormatBasics
        6dc691300ced97c0e319cbcc0a715044        Bytes
        3bd1af04573ce2da7fc3dc04403e852e        Buffer
        383683999ce4d4a54f1689bb92969ecb        BatStream
        fbefc52bb310bf525973099141e16ffe        BatOrd
        92bc9ee9d7e3da3421ed7fc5c0ade74d        BatInterfaces
        7d12ec9e52c91f3af313796ff85158c4        BatInnerIO
        6f57ab9f63c2f00619c3ffc9bde0bc80        BatIO
        bd48c0243cabeabfa9ba81aa02319882        BatEnum
        1972feae99a1525e1b830ca37c4efa20        BatConcurrent

我们导入了两个名称相同但实现不同(CRC 和不同)的接口。第一个接口实际上是你的Stream模块的接口,第二个是标准的Stream模块的接口:

$ ocamlobjinfo /home/ivg/.opam/devel/lib/ocaml/stream.cmi
File /home/ivg/.opam/devel/lib/ocaml/stream.cmi
Unit name: Stream
Interfaces imported:
        d0b21ad0c1f4e93fa8c05b9ded519b52        Stream
        999b28e3b7638771c87eebf5a8325e42        Pervasives
        9642e3ed163e46770985ca668738ed5f        CamlinternalFormatBasics

您可能会注意到每个模块总是导入自己的接口。所以冲突发生在你的 Stream 模块和 OCaml 的 Stream 模块之间。标准的 Stream 模块通过 BatStream 模块进入您的编译单元。

总结一下。接口命名空间是扁平的,因此需要使用前缀来防止冲突,参见BatStream。是的,它很丑。

模块打包可以帮助您防止打包到包中的模块与使用该包的模块之间的名称冲突。例如,如果你有一个模块 M 打包在包 P 中,那么你可以 link 它与另一个模块 M 并且 [=19] 之间不会发生冲突=] 和 P.M (如果你做的一切都正确)。但是,当您构建包时,构成它的模块不应与它们使用的模块冲突,不幸的是,OCaml 标准库不是包,因此您应该选择不与标准库冲突的名称或您用于实施包的任何其他库。