module <name> =
struct
..
end;;
module type <name> =
struct (* should have been sig *)
..
end;;
第一个声明了一个模块,第二个声明了一个模块类型(也称为签名)。模块类型包含type
和val
声明,而模块可以包含定义(例如let
绑定)。您可以使用签名来限制模块的类型,就像您可能为函数一样。例如,
module type T = sig
val f : int -> int
end
module M : T = struct
let f x = x + 1
let g x = 2 * x
end
# M.f 0 ;;
- : int = 1
# M.g 0 ;;
Error: Unbound value M.g
M.g
未绑定,因为它被T
签名隐藏。
另一种常见的使用模块类型的方式是将其作为函子的参数和返回值。例如,在标准库中的Map.Make
函子接受具有Map.OrderedType
签名的模块,并创建具有Map.S
签名的模块。
P.S. 请注意,问题中存在错误。模块类型是使用声明的。
module type <name> = sig
...
end
struct ... end
)是一组定义。语言中的任何对象都可以在一个模块中进行定义:核心值(let x = 2 + 2
),类型(type t = int
),模块(module Empty = struct end
),签名(module type EMPTY = sig end
),等等。 模块是结构的一般化:结构是一个模块,而一个函子也是一个模块(将其视为一个以模块为参数并返回新模块的函数)。模块类似于核心值,但位于更高的级别上:一个模块可以包含任何内容,而核心值只能包含其他核心值¹。
签名(写成sig ... end
)是一组规范(有些语言使用术语声明)。语言中的任何对象都可以在一个模块中进行规定:核心值(val x : int
),类型(type t = int
),模块(module Empty : sig end
),签名(module type EMPTY = sig end
),等等。 模块类型概括了签名:一个模块类型指定了一个模块,而指定结构的模块类型称为签名。模块类型是对于核心值的普通类型而言的。
编译单元(.ml
文件)是结构。接口(.mli
文件)是签名。
因此,module Foo = struct ... end
定义了一个名为Foo
的模块,它恰好是一个结构。这类似于let foo = (1, "a")
定义了一个名为foo
的值,它恰好是一个对。而module type FOO = sig ... end
(注意:sig
而不是struct
)定义了一个名为FOO
的模块类型,它恰好是一个签名。这类似于type foo = int * string
定义了一个名为foo
的类型,它恰好是一个乘积类型。
¹ 实际上,自OCaml 3.12引入一级模块以来,这个说法已经不再正确,但对于介绍性演示来说足够接近了。
模块类型描述了一个模块。这就像.ml和.mli之间的区别一样。