假设我有一个简单的模块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'
子句允许您注释模块签名以向其添加类型信息。它非常有用,特别是在处理仿函数时。有关更多信息,请参阅here。
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
,这样您就不会遇到任何双重定义问题。有关详细信息,请参阅here。
正如你所指出的,这可能不是你想要的方法,因为你不再容易知道MyFoo
确实是一个BaseFoo
。