OCaml交叉链接

6
在OCaml中,引用链接是如何工作的?
例如,假设我声明了3个模块:
- A.ml - B.ml - C.ml
其中:
- A 需要 B 和 C - B 需要 A
在编译时应该怎么处理呢?由于顺序很重要,使用 ocamlc 或 ocamlopt 时,如何解决 B 和 A 之间的交叉引用问题?
我试图首先使用 ocamlc -c 将它们全部编译成 .cmo,然后将它们全部链接在一起,但没有成功,因为交换参数只会将问题从一个模块转移到另一个模块。
具体的错误是:
Error: Error while linking A.cmo: Reference to undefined global `B'
(如果我交换参数的顺序,情况也会反过来)
我认为这是一个简单的问题,但我无法解决它。谢谢您的帮助。

能否将A分解为B所需的部分和依赖于B的部分?或者它们是相互包含的吗? - Niki Yoshiuchi
2个回答

7

您需要将模块合并为一个文件并使其递归。我认为没有一种方法可以从两个单独的文件的编译过程中完成此操作。

module rec A : 
    sig
        val f : int -> int
        val g : int -> int
    end = 
    struct
        let f x = (B.g x) + 1
        let g x = x + 1
    end
and B :
    sig
        val f : int -> int
        val g : int -> int
    end = 
    struct
        let f x = (A.g x) + 1
        let g x = x + 1
    end
编辑:根据您的评论,我猜测您在同一个文件中有解析器的类型定义以及处理/操作该类型的函数。我同意您的观点,这是有道理的。但正如您所遇到的那样,如果该文件不仅要操作该类型还要调用解析器来生成数据,那么解析器该如何构造呢?我的解决方案是将类型分离为它自己的模块,并在执行操作的模块中打开该模块。

因此,您正在将A拆分为(AA'),其中A'包含由B产生并在A中使用的类型。您的依赖关系变为:

  • A需要A'BC
  • B需要A'

例如,我有一个用于启动我编写的任何应用程序的配置文件解析器。

ConfType   --contains the type t  
Conf       --calls parser, and contains helper functions for type ConfType.t  
ConfParser --for ocamlyacc
ConfLexer  --for ocamllex

使用多态变量是解决这个问题的一种替代方案。这样可以消除依赖性,因为它们是按需定义的。当然,由解析器生成的类型可能与Conf中的类型不同,编译器无法帮助您解决错误。


这可能是一个问题,因为其中一个模块是由 ocamlyacc 生成的,所以我无法直接控制 ml/mli 文件。 - Jack
我认为我遇到了同样的问题。如果我说对了,请告诉我。 - nlucaroni
谢谢您详细的解释,它确实帮助了很多,使项目结构更加清晰 :) - Jack

3
Ocaml语言支持模块之间的递归,但编译器不支持编译单元之间的递归。因此,你不能让A.ml需要B.ml,而B.ml又需要A.ml。如果可以轻松地进行重构以消除递归,则最好这样做,但让我们假设你不能这样做。
如nlucaroni所解释的那样,一种解决方法是将两个模块收集到同一个文件中并使用module rec。另一种解决方法有时是将一个模块转换为functor,例如将A转换为带有B签名参数的functor F,然后首先编译定义F的文件,然后是B,最后是一个仅定义module A = F(B)的文件。
Ocamlyacc确实使事情更加复杂,但你可以欺骗它!你可以在.mly头部写入module A = functor (...) -> struct,并在页脚中添加匹配的end。但是,你将不得不重写生成的.mli,以在构建过程中添加module A : functor (...) -> sigend。(我知道我以前做过这件事,为了解决你现在遇到的同样的问题,尽管我不记得具体是在哪里,所以无法给出真实的例子。)
另一个值得探索的可能性是从Ocamlyacc切换到Menhir,它是Ocamlyacc的替代品(语法相同,几乎不需要移植),具有一些很好的功能,可以帮助你,例如支持参数化解析器模块(即functors)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接