假设我有一个看起来像这样的简单模块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.
答案 0 :(得分:3)
发生的事情几乎就是您所描述的:给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
。