假设我有一个简单的模块MyFoo
看起来像这样
module MyFoo = struct
type t =
| A of string
| B of int
let to_string thing =
match thing with
| A str -> str
| B n -> string_of_int n
end
有了这个定义,它工作得很好,而且符合预期——我可以做一些类似的事情
let _ = MyFoo.A "";;
- : MyFoo.t = MyFoo.A ""
没有任何问题。
现在也许我想创建一个使用具有此结构的模块的函子,因此我定义了一个模块签名,该签名通常描述了它的外观并将其称为BaseFoo
module type BaseFoo = sig
type t
val to_string : t -> string
end
如果我以相同的方式重新定义MyFoo
但给它这个签名,比如
module MyFoo : BaseFoo = struct
type t =
| A of string
| B of int
let to_string thing =
match thing with
| A str -> str
| B n -> string_of_int n
end
我失去了其类型的精度t
(有没有更好的方法来描述这里发生的事情?( - 例如:
let _ = MyFoo.A "";;
Error: Unbound constructor MyFoo.A
这里到底发生了什么,为什么会发生这种情况?有没有处理此类问题的规范方法(除了省略签名(?
我也尝试手动包括签名和特定类型类型定义,但遇到了不同类型的错误(这可能不是正确的方法(。
module MyFoo : sig
include BaseFoo
type t = | A of string | B of int
end = struct
type t =
| A of string
| B of int
let to_string thing =
match thing with
| A str -> str
| B n -> string_of_int n
end
let _ = MyFoo.A "test";;
Error: Multiple definition of the type name t.
Names must be unique in a given structure or signature.
你不需要签名
正在发生的事情几乎就是你所描述的:在其定义中给MyFoo
BaseFoo
签名会将其限制为签名。
为什么?因为这就是在这个地方指定签名的目的。规范的解决方案是保留签名(通常,让模块定义旁边的签名定义对读者来说足够清晰(。
请注意,当您在函子上调用MyFoo
时,将检查签名。我通常的选择是依靠它。
一些解决方法
鉴于您尝试过的内容,我想这对您来说可能很有趣:
module type BaseFoo = sig ... end
module MyFoo = struct ... end
module MyFooHidden : BaseFoo = MyFoo (* Same as defining MyFoo : BaseFoo *)
module MyFooWithType :
BaseFoo with type t = MyFoo.t
= MyFoo (* What you want *)
with type t = t'
子句允许您批注模块签名以向其添加类型信息。它非常有用,尤其是在处理函子时。有关更多信息,请参阅此处。
MyFooHidden
可能看起来毫无用处,但您可以将其视为检查MyFoo是否具有正确的签名。毕竟,您仍然可以随心所欲地使用MyFoo
。MyFooWithType
实际上(有点(不太有用,因为如果您更改签名以添加要导出的类型,则也需要在此处添加导出。
使用包含
至于你的include
尝试。嗯,不错的尝试!你快到了:
module MyFoo : sig
type t = A of string | B of int
include BaseFoo with type t := t
end
with type t := t'
有点不同,因为它不执行相等而是替换。类型t
定义从BaseFoo
签名中完全删除,所有实例都替换为您自己的t
,这样您就不会遇到任何双重定义问题。有关更多详细信息,请参阅此处。
正如您所指出的,这可能不是您想要的方法,因为您不再容易知道MyFoo
确实是一种BaseFoo
。