我正在尝试实现一个在其他模块中重用模块的小例子,但是到目前为止,我还没有完全成功。
我想要实现的是创建2个签名-S和SE(SE包括S),以及2个模块-M(实现S)和ME(实现SE),目的是不重复先前的代码。
我所缺少的是包括我内部M的内容:
module type S = sig
type t
val of_int : int -> t
end
module type SE = sig
include S
val to_string : t -> string
end
module M : S = struct
type t = int
let of_int x = x
end
module ME : SE = struct
(* include M *)
let to_string = string_of_int
end
到目前为止,通过在ME中取消注释(* include M *)
并将M的定义更改为module M : S with type t = int = struct
,找到了一种解决方法,但是这种做法无法实现此目的,因为它从实现S更改了M的定义实现类似S的东西。
当然,此练习必须有适当的解决方案。那我想念什么?
答案 0 :(得分:2)
问题是,一旦将M
的签名限制为S
,就没有足够的有关M.t
的信息来实现有意义的to_string
功能。 / p>
实际上,类型为S
的模块定义了一个黑匣子(抽象类型)t
,它可以由整数产生……仅此而已。换句话说,您只能产生M.t
类型的值,而对这些值的内容一无所知。因此,to_string
剩下的唯一选择是忽略其参数,并返回不相关的字符串。例如,
module ME: SE = struct
include M
let to_string _ = "?"
end
以相反的顺序定义M
和ME
效果更好。首先,我们使用更为详尽的API定义模块:
module ME : SE = struct
type t = unit
let of_int _ = ()
let to_string () = "()"
end
然后我们可以使用签名约束来擦除to_string
函数
module M: S = ME
另一种选择是避免使用精确的模块类型t
或让编译器进行推断,从而使S with type t = int
类型变得抽象,如您所发现。