我对Ocaml中的模块类型感到困惑。
我想知道在哪种情况下我们应该使用模块类型?
我通常在.mli中使用模块sig来公开一些细节,并在.ml中放置相应的实现模块结构。
例如:
.mli
module A:
sig
type t = T of string
end
.ml
module A =
struct
type t = T of string
end
出于这个原因,我认为Ocaml的模块就像C.中的.h和.c文件一样。
我知道模块类型可以声明一个接口,但是接口与Java的接口不同。
就像书中的一个例子:
open Core.Std
module type ID = sig
type t
val of_string : string -> t
val to_string : t -> string
end
module String_id = struct
type t = string
let of_string x = x
let to_string x = x
end
module Username : ID = String_id
module Hostname : ID = String_id
type session_info = { user: Username.t;
host: Hostname.t;
when_started: Time.t;
}
let sessions_have_same_user s1 s2 =
s1.user = s2.host
上面的代码有一个错误:它将一个会话中的用户名与另一个会话中的主机进行比较,在两种情况下应该比较用户名。
似乎模块类型无法为其实现提供新的通用超类型。
什么是模块类型的真正应用?
这里,模块类型用于将类型t
隐藏到模块的用户。
当一个人使用Username.t
或Hostname.t
时,不能依赖那些类型的字符串,整数或任何特定类型。模块类型使它们不透明,就好像它们不是模块接口的一部分,而只是模块编写器将来要改变的实现细节。
基本上,模块的用户只能通过模块功能对t
进行操作。
编译器检查用户代码是否对这些类型t
实际上是什么做出任何假设,以便将来模块编写者可以在不破坏用户代码的情况下更改它们。
正如已经提到的,这里模块类型用于实现类型t
的更抽象的视觉。当你将Username
和Hostname
定义为两个独立的模块时,它意味着逻辑上它们必须是不同的,并且这个模块的签名是唯一可能的方法。
但是当你说:你说错了:
似乎模块类型无法为其实现提供新的通用超类型。
实际上你可以确切地说Username.t
和Hostname.t
是相同的类型。
module Username = (String_id : ID with type t = String_id.t)
module Hostname = (String_id : ID with type t = String_id.t)
Username.to_string (Hostname.of_string "hi")
这里没用,但有时它真的很有帮助。
module types与functors密切相关。阅读the module system章节。
仿函数的参数有一些模块类型,结果也是如此。
Functor被大量使用,大多数标准容器(例如Set
-s或Map
-s)都是仿函数。
如果您开发通用容器,您肯定会需要仿函数和模块类型。
(所以仿函数和模块类型在Ocaml中非常有用,而模板在C ++中很有用)