模块签名丢失类型精度

时间:2018-09-17 02:06:21

标签: ocaml

假设我有一个看起来像这样的简单模块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.

1 个答案:

答案 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具有正确签名的检查。毕竟,您仍然可以使用MyFooMyFooWithType实际上(有点)没用,因为如果您更改签名以添加要导出的类型,则也需要在此处添加导出。

使用包含

至于您的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