Pattern matching on a GADT fails

。_饼干妹妹 提交于 2019-12-08 03:43:45

问题


I was playing around a bit more with ReasonML and found pattern matching on type t from the following example to not be working with the error

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;

Now on some level this makes sense to me if this was about the return type of a function.

I found an answer (I think) to an equivalent question. For the second section the answer seems to be to kinda ignore the bound type of the constructor. Is the same possible in ReasonML?

P.s.: please correct me pedantically on terminology, I'm still learning what's what.

P.p.s.: I know I could work around the original problem by explicitly typing x but I really like the fun syntax because it's fun.


回答1:


The brief answer is that GADTs make the type system too expressive to be fully inferred. For instance, in your case, the following functions are both total (aka they handle all possible values of their input

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

You can check that they are total by adding an explicit refutation clause in OCaml syntax (Reason syntax has not yet be updated to include those):

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

Here the dot . means that the pattern described on the left-hand side of the clause is syntactically valid, but does not refer to any actual value due to some type constraints.

Consequently, you need to tell the type checker that you intend to match of a value of type t(a) for any a, this needs to be done with locally abstract types:

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

With this locally abstract annotation, the type checker knows that it is not supposed to replace this variable a by a concrete type globally (aka it should consider that locally a is some unknown abstract type), but it can refine it locally when matching a GADT.

Strictly speaking, the annotation is only needed on the pattern, thus you can write

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

Note that for recursive functions with GADTs, you may need to go for the full explictly polymorphic locally abstract type notations:

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)

where the difference is that eval is recursively polymorphic. See https://caml.inria.fr/pub/docs/manual-ocaml-4.09/polymorphism.html#sec60 .

EDIT: Annotating the return type

Another annotation that is often needed to avoid the dreaded "this type would escape its scope" is to add an annotation when leaving a pattern matching. A typical example would be the function:

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

There is an ambiguity here because inside the branch One, the typechecker knows that int=a but when leaving this context, it needs to choose one side of the equation or the other. (In this specific case, left at its own device the typechecker decides that (0: int) is the more logical conclusion because 0 is an integer and this type has not been in contact in any way with the locally abstract type a .)

This ambiguity can be avoided by using an explicit annotation, either locally

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

or on the whole function

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



回答2:


Someone will probably come around soon to give a proper explanation, but the short answer is that you need to use a locally abstract type in place of the type variable.

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

Why? Well that's still a mystery to me, especially in this case where it's just a phantom type and no actual values of the type is involved. But I suspect it's explained at least partly by this paragraph (or the following) 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.



来源:https://stackoverflow.com/questions/58964775/pattern-matching-on-a-gadt-fails

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!