GADT 上的模式匹配失败

Pattern matching on a GADT fails

我更多地使用了 ReasonML,发现以下示例中 type t 上的模式匹配无法处理错误

Error: This pattern matches values of type t(float) but a pattern was expected which matches values of type t(int) Type float is not compatible with type int

  type t('a) =
    | One: t(int)
    | Two: t(float);

  let x =
    fun
    | One => None
    | Two => None;

现在在某种程度上,如果这是关于函数的 return 类型,这对我来说很有意义。

我找到了答案(我认为)to an equivalent question。对于第二部分,答案似乎有点忽略构造函数的绑定类型。在 ReasonML 中也可以吗?

P.s.: 请迂腐地指正术语,我还在学习什么是什么。

P.p.s.: 我知道我可以通过显式输入 x 来解决原始问题,但我真的很喜欢 fun 语法,因为它很有趣。

可能很快就会有人给出正确的解释,但简短的回答是您需要使用局部抽象类型来代替类型变量。

let x: type a. t(a) => option(a) =
  fun
  | One => None
  | Two => None;

为什么?嗯,这对我来说仍然是个谜,尤其是在这种情况下,它只是一个幻影类型,不涉及该类型的实际值。但我怀疑这一段(或以下)至少部分地解释了它 from the manual:

Type inference for GADTs is notoriously hard. This is due to the fact some types may become ambiguous when escaping from a branch. For instance, in the Int case above, n could have either type int or a, and they are not equivalent outside of that branch. As a first approximation, type inference will always work if a pattern-matching is annotated with types containing no free type variables (both on the scrutinee and the return type). This is the case in the above example, thanks to the type annotation containing only locally abstract types.

简短的回答是 GADT 使类型系统的表现力太强而无法完全推断。 例如,在您的情况下,以下函数都是总函数(也就是它们处理输入的所有可能值

let one = (One) => None
let two = (Two) => None

您可以通过在 OCaml 语法中添加显式反驳子句来检查它们是否完整(原因语法尚未更新以包含这些):

let one = function
| One -> None
| _ -> .

这里的点 . 表示子句左侧描述的模式在句法上是有效的,但由于某些类型限制而不引用任何实际值。

因此,您需要告诉类型检查器您打算为任何 a 匹配类型 t(a) 的值,这需要使用本地抽象类型来完成:

let x (type a, (x:t(a))) = switch(x){
| One => None
| Two => None
}

有了这个本地抽象注解,类型检查器知道它不应该在全局范围内用具体类型替换这个变量 a(也就是说它应该考虑局部 a 是一些未知的抽象类型),但它可以在匹配 GADT 时在本地对其进行细化。

严格来说,注解只需要在pattern上,所以可以这样写

let x (type a) = fun
| (One:t(a)) => None
| Two => None

请注意,对于具有 GADT 的递归函数,您可能需要使用完整的显式多态局部抽象类型表示法:

type t(_) =
| Int(int): t(int)
| Equal(t('a),t('a)):t(bool)

let rec eval: type any. t(any) => any = fun
| Int(n) => n
| Equal(x,y) => eval(x) = eval(y)

不同之处在于 eval 是递归多态的。参见 https://caml.inria.fr/pub/docs/manual-ocaml-4.09/polymorphism.html#sec60

编辑:注释 return 类型

通常需要避免可怕的 "this type would escape its scope" 的另一个注解是在离开模式匹配时添加注解。 一个典型的例子是函数:

let zero (type a, (x:t(a)) = switch (x){
| One => 0
| Two => 0.
}

这里有一个歧义,因为在分支 One 内,类型检查器知道 int=a 但是当离开这个上下文时,它需要选择等式的一侧或另一侧。 (在这种特定情况下,类型检查器决定 (0: int) 是更合乎逻辑的结论,因为 0 是一个整数,并且此类型没有以任何方式与本地抽象类型联系a .)

可以通过在本地使用显式注释来避免这种歧义

let zero (type a, (x:t(a))) = switch (x){
| One => ( 0 : a )
| Two => ( 0. : a )
}

或整个函数

let zero (type a): t(a) => a = fun
| One => 0
| Two => 0.