.mli 文件单独定义模块的接口,编译它们时根本不使用 .ml 文件。实际上,您可以为由多个 .ml 文件组成的包创建一个 .mli 文件。永远不会神奇地将 .ml 文件中的某些内容拉入界面。
现在,与在 .ml 文件中一样,在 .ml 文件中指定类型有三种方式:
1) 作为抽象类型。没有任何类型的暴露:
# type some_type;;
type some_type
# let v = This;;
Error: Unbound constructor This
# let to_int = function This -> 1 | That -> 2;;
Error: Unbound constructor This
这从外部隐藏了类型的详细信息,允许模块稍后随意更改类型而不会破坏任何源代码。它也用于没有值或外部值(参见手册中的 C 接口)的非 ocaml 类型的幻像类型。
2) 作为公共类型。暴露了类型的结构,可以创建值:
# type some_type = This | That;;
type some_type = This | That
# let v = This;;
val v : some_type = This
# let to_int = function This -> 1 | That -> 2;;
val to_int : some_type -> int = <fun>
这与第一种情况相反。一切都是公开的。
但还有第三种选择:
3) 作为私有类型。暴露了类型的结构,但无法创建值:
# type some_type = private This | That;;
type some_type = private This | That
# let v = This;;
Error: Cannot create values of the private type some_type
# let to_int = function This -> 1 | That -> 2;;
val to_int : some_type -> int = <fun>
这在 1 和 2 之间。用例是当您需要控制值的构造时。例如,考虑一个包含小于 100 的小整数的类型。你可以这样写:
# let make x =
if x < 0 || x >= 100
then raise (Invalid_argument "Out of range")
else x;;
val make : int -> int = <fun>
然后将 .mli 文件编写为:
type t = private int;;
val make : int -> t;;
这确保了 t 类型的值只能使用 make 函数来构造。任何期望类型 t 的东西都只会接受由 make 构造的类型 t 的值。另一方面,任何期望 int 类型的东西也将接受 t 类型的值。后者不会是抽象类型的情况。