如何在 OCaml 中跨模块使用 GADT 而不引发警告?

How to use GADTs across modules in OCaml without raising warnings?

我有两个文件:gadt1.ml 和 gadt2.ml,第二个文件依赖于第一个文件。

gadt1.ml:

type never
type _ t1 = A1 : never  t1 | B1 : bool t1
type _ t2 = A2 : string t2 | B2 : bool t2
let get1 : bool t1 -> bool = function B1 -> true
let get2 : bool t2 -> bool = function B2 -> true

gadt2.ml:

let get1 : bool Gadt1.t1 -> bool = function Gadt.B1 -> true
let get2 : bool Gadt1.t2 -> bool = function Gadt.B2 -> true

当我使用 ocaml 4.02.3 (ocamlbuild gadt2.native) 进行编译时,我收到警告 8,提示函数 Gadt2.get1 未详尽无遗。我很困惑 Gadt2.get1 会发出警告,而 Gadt1.get1Gadt2.get2 不会。

我的假设是空类型 never 不能等于 bool 所以 Gadt2.get1 不应该发出警告。另一方面,如果我用参数 A1 调用 Gadt2.get1,我会得到一个类型错误(根据需要)。警告是预期行为还是错误?我错过了什么?

顺便说一下,将 -principal 添加到编译标志不会改变任何内容。

Gadt2只能看到Gadt1的接口,看不到它的实现。界面看起来像这样:

type never
type _ t1 = A1 : never  t1 | B1 : bool t1
type _ t2 = A2 : string t2 | B2 : bool t2
val get1 : bool t1 -> bool
val get2 : bool t2 -> bool

请注意 type never 是抽象的——没有什么可以阻止给它 RHS 的实现。特别是,您可以在 gadt1.ml 中写入 type never = bool,此时将 A1 传递给 get1 变得合理,因此 get1 需要为这种可能性做好准备。

相比之下,string 是一个非抽象类型:它有一个已知的表示,所以它不可能等于 bool,因此 A2 永远不会传递给 get2.

你似乎想用 never 做的是声明一个类型,它不是抽象的而是 empty,将其表示暴露为根本没有构造函数。不幸的是,OCaml 并没有很好地支持它;定义编译器可以在本地判断为空的类型的能力有点奇怪,manual 中并未真正提及。无法在模块签名中表达 "this type is empty"。

更新:我相信这已经改变了,你现在可以写:

type never = |

表明 never 确实是一个空类型,而不是抽象类型。

正如 Ben 所说,问题在于签名 type t 是抽象的而不是空的。一种替代方法是定义一个无人居住的类型:

module One = struct
  type never = { impossible : 'a . 'a }
  type _ t1 = A1 : never  t1 | B1 : bool t1
  type _ t2 = A2 : string t2 | B2 : bool t2
  let get1 : bool t1 -> bool = function B1 -> true
  let get2 : bool t2 -> bool = function B2 -> true
end

module Two = struct
  let get1 : bool One.t1 -> bool = function One.B1 -> true
  let get2 : bool One.t2 -> bool = function One.B2 -> true
end

这不会给出任何详尽警告。

我今天 运行 遇到了同样的问题,并且花了很多时间寻找解决方案。 正如 Ben 和 gsg 所说,确实是因为 type never 是抽象的。 我认为最优雅的解决方案是将其定义为私有变体。

type never = private Never

这解决了详尽警告并清楚地表明您消除了创建此类变量的可能性。