第 12 章 语言扩展

8 类型级模块别名

(在 OCaml 4.02 中引入)

规范::= ...
 module模块名=模块路径

上述规范在签名内部,只匹配等于 模块路径 的模块定义。反之,类型级模块别名可以被自身匹配,也可以被它引用的模块类型的任何超类型匹配。

模块路径 有一些限制

  1. 它应该采用 M0.M1...Mn 的形式( 没有函子应用);
  2. 在函子的主体内部,M0 不应该是函子参数之一;
  3. 在递归模块定义内部,M0 不应该是递归定义的模块之一。

这些规范也会被推断。也就是说,当 P 是满足上述约束的路径时,

module N = P

具有类型

module N = P

类型级模块别名用于检查模块路径的相等性。也就是说,在一个已知模块名 NP 的别名的上下文中,不仅这两个模块路径被检查为相等,而且 F(N) 和 F(P) 也被识别为相等。在默认的编译模式下,这与之前模块别名仅具有与其引用的模块相同的模块类型的做法唯一的区别。

当启用编译器标志 -no-alias-deps 时,类型级模块别名也被用来避免在编译单元之间引入依赖关系。也就是说,引用另一个编译单元中模块的模块别名不会引入对该编译单元的链接时依赖关系,只要它没有被取消引用;如果需要读取接口,它仍然会引入编译时依赖关系, 如果该模块是编译单元的子模块,或者引用了一些类型组件。此外,访问模块别名会引入对包含别名引用的模块的编译单元的链接时依赖关系,而不是包含别名的编译单元。请注意,链接时行为的这些差异可能与之前的行为不兼容,因为某些编译单元可能不会从库中提取,并且它们的副作用会被忽略。

这些弱化的依赖关系使得可以在 -pack 机制的位置使用模块别名。假设您有一个名为 Mylib 的库,它由模块 AB 组成。使用 -pack,您将发出以下命令行

ocamlc -pack a.cmo b.cmo -o mylib.cmo

并因此获得一个 Mylib 编译单元,其中包含 AB 作为子模块,并且与它们各自的编译单元没有依赖关系。这是一个可能的替代方法的具体示例

  1. 将包含 AB 的文件重命名为 Mylib__AMylib__B
  2. 创建一个打包接口 Mylib.ml,其中包含以下行。
    module A = Mylib__A
    module B = Mylib__B
    
  3. 使用 -no-alias-deps 编译 Mylib.ml,并使用 -no-alias-deps-open Mylib 编译其他文件(最后一个等价于在每个文件的顶部添加行 open! Mylib)。
    ocamlc -c -no-alias-deps Mylib.ml
    ocamlc -c -no-alias-deps -open Mylib Mylib__*.mli Mylib__*.ml
    
  4. 最后,创建一个包含所有编译单元的库,并导出所有编译的接口。
    ocamlc -a Mylib*.cmo -o Mylib.cma
    

这种方法允许您在库内部直接访问 AB,并在库外部以 Mylib.AMylib.B 的形式访问它们。它还有一个优点,即 Mylib 不再是单一的:如果您使用 Mylib.A,则只会链接 Mylib__A,而不是 Mylib__B

请注意在 Mylib__AMylib__B 中使用双下划线。这些是故意选择的;编译器在打印路径时使用以下启发式方法:给定路径 Lib__fooBar,如果 Lib.FooBar 存在并且是 Lib__fooBar 的别名,则编译器将始终显示 Lib.FooBar 而不是 Lib__fooBar。这样,较长的 Mylib__ 名称将保持隐藏,用户看到的只有更友好的点名称。这就是 OCaml 标准库的编译方式。