Module OCaml模块类型和单独编译
我正在通读(信中亲切地指着我)。我知道本文讨论了OCaml当前模块类型/签名系统的起源。为此,作者提出了签名中类型声明的不透明解释(允许单独编译)以及清单类型声明(用于表达)。为了将我自己的一些示例放在一起,以演示OCaml模块签名符号试图解决的问题,我在两个文件中编写了以下代码: 在文件Module OCaml模块类型和单独编译,module,ocaml,Module,Ocaml,我正在通读(信中亲切地指着我)。我知道本文讨论了OCaml当前模块类型/签名系统的起源。为此,作者提出了签名中类型声明的不透明解释(允许单独编译)以及清单类型声明(用于表达)。为了将我自己的一些示例放在一起,以演示OCaml模块签名符号试图解决的问题,我在两个文件中编写了以下代码: 在文件ordering.ml(或.mli)中,我两个都试过了(文件A): 在文件useOrdering.ml(文件B)中: 这种想法是希望编译器(在编译第二个文件时)抱怨模块StringOrdering上没有足够的类
ordering.ml
(或.mli
)中,我两个都试过了(文件A):
在文件useOrdering.ml
(文件B)中:
这种想法是希望编译器(在编译第二个文件时)抱怨模块StringOrdering
上没有足够的类型信息来检查StringOrdering.isLess
应用程序(从而激发对类型语法的需求)。
然而,尽管文件A按预期编译,但文件B导致3.11.2ocamlc
抱怨语法错误。我理解签名意味着允许某人基于模块签名编写代码,而无需访问实现(模块结构)
我承认,我不确定我在中遇到的语法:模块A:B
,但这让我想知道是否存在这样或类似的语法(不涉及函子),允许某人仅基于模块类型编写代码,并在链接时提供实际的模块结构,类似于在c/c++中如何使用*.h
和*.c
文件。如果没有这种能力,模块类型/签名似乎基本上是用于密封/隐藏模块内部或更显式的类型检查/注释,而不是用于单独/独立编译
事实上,看看这个例子,我与C编译单元的类比似乎被打破了,因为OCaml手册将OCaml编译单元定义为A.ml
和A.mli
duo,而在C/C++中,.h
文件被粘贴到任何导入的.C
文件的编译单元。正确的方法是执行以下操作:
在ordering.mli中写入:
(* This define the signature *)
module type ORDERING = sig
type t
val isLess : t -> t -> bool
end
(* This define a module having ORDERING as signature *)
module StringOrdering : ORDERING
编译文件:ocamlc-c ordering.mli
在另一个文件中,请参阅编译后的签名:
open Ordering
let main () =
Printf.printf "%b" (StringOrdering.isLess "a" "b")
let () = main ()
编译文件时,会出现预期的类型错误(即string
与Ordering.StringOrdering.t
不兼容)。如果要删除类型错误,应在ordering.mli
中的StringOrdering
定义中添加类型为t=string的约束
所以请回答第二个问题:是的,在字节码模式下,编译器只需要知道所依赖的接口,并且可以选择在链接时使用哪个实现。默认情况下,本机代码编译不是这样(因为模块间优化),但可以禁用它 您可能只是被显式模块和签名定义之间的关系以及通过.ml/.mli文件隐式定义模块之间的关系弄糊涂了
基本上,如果您有一个文件a.ml并在其他文件中使用它,那么就好像您编写了
module A =
struct
(* content of file a.ml *)
end
module A :
sig
(* content of file a.mli *)
end =
struct
(* content of file a.ml *)
end
如果您也有a.mli,那么就好像您已经编写了
module A =
struct
(* content of file a.ml *)
end
module A :
sig
(* content of file a.mli *)
end =
struct
(* content of file a.ml *)
end
请注意,这只定义了名为a的模块,而不是模块类型。不能通过此机制为A的签名命名
使用的另一个文件可以单独针对.mli进行编译,而根本不提供.ml。但是,您需要确保所有类型信息在需要时都是透明的。例如,假设要定义整数上的映射:
(* intMap.mli *)
type key = int
type 'a map
val empty : 'a map
val add : key -> 'a -> 'a map -> 'a map
val lookup : key -> 'a map -> 'a option
...
在这里,key
是透明的,因为任何客户端代码(此签名描述的模块IntMap
的)都需要知道它是什么,才能向映射添加内容。然而,map
类型本身可以(也应该)保持抽象,因为客户端不应该弄乱它的实现细节
与C头文件的关系是,这些头文件基本上只允许透明类型。在Ocaml中,您可以选择。模块字符串排序:排序是一种模块声明。您可以在签名中使用它,即签名包含一个名为StringOrdering
的模块字段,并具有签名ORDERING
。这在模块中没有意义
您需要在某处定义一个模块来实现所需的操作。模块定义可以类似于
module StringOrderingImplementation = struct
type t = string
let isLess x y = x <= y
end
然后,StringOrderingImplementation.isLess“a”“b”
类型正确,而StringOrderingAbstract.isLess“a”“b”
无法键入,因为StringOrderingAbstract.t
是一种抽象类型,与string
或任何其他先前存在的类型不兼容。实际上,不可能构建类型为StringOrderingAbstract.t
的值,因为模块不包含任何构造函数
当您有一个编译单元foo.ml
时,它是一个模块foo
,该模块的签名由接口文件foo.mli
给出。也就是说,文件foo.ml
和foo.mli
等同于模块定义
module Foo = (struct (*…contents of foo.ml…*) end :
sig (*…contents of foo.mli…*) end)
当编译使用Foo
的模块时,编译器只查看Foo.mli
(或者更确切地说是其编译结果:Foo.cmi
),而不是查看Foo.ml
)。这就是接口和单独编译如何结合在一起的。C需要#include
,因为它缺少任何形式的名称空间;在OCaml中,Foo.bar
自动引用编译单元Foo
中定义的bar
,前提是在sco中没有其他名为Foo
的模块
module Foo = (struct (*…contents of foo.ml…*) end :
sig (*…contents of foo.mli…*) end)