GADT模式匹配

GADT pattern matching

我最近一直在玩 GADT,想知道是否有人可以为我指明正确的方向,让我学习如何输入它以便编译,如果可能的话,或者我是否让事情过于复杂。

我在这里看到了一些关于 GADT 模式匹配的其他答案,但这似乎有点不同。

我见过这种表示没有可能值的类型的事情:

module Nothing = {
  type t =
    | Nothing(t);
};

所以我想用它来锁定这个 Exit.t 类型,这样我就可以有一个 Exit.t('a, Nothing.t) 的类型来表示成功案例,捕获事实上没有可恢复的失败值。

module Exit = {
  type t('a, 'e) =
    | Success('a): t('a, Nothing.t)
    | Failure('e): t(Nothing.t, 'e);

这似乎没问题,直到我尝试为它编写一个 flatMap 函数。

  let flatMap: ('a => t('a1, 'e), t('a, 'e)) => t('a1, 'e) = (f, exit) =>
    switch (exit) {
    | Success(a) => f(a)
    | Failure(_) as e => e
    };
};

照原样,它推断类型 Exit.t 总是 Exit.t(Nothing.t, Nothing.t) 我有点理解,因为失败中的类型案例会将第一种类型设置为 Nothing,成功案例会将第二种类型设置为 Nothing。

我尝试了我所知道的一件事,使用 type a 使其中一些类型成为本地类型。我已经尝试 type a a1 etype a e 离开 'a1 但我似乎无法理解这个想法。

(我在下面使用 OCaml 语法,因为这个问题也被标记为“ocaml”,但对于 Reason 来说也是如此。)

首先,您的类型 Nothing.t 不为空。循环值 Nothing (Nothing (Nothing (...))) 是一个有效的居民。如果你真的想让类型为空,就不要放任何构造函数。

其次,正如您所猜测的,在 flat_map 中,您的 Failure 分支强制 'a1Nothing.t 实例化。没有办法解决这个问题;这不是编译器的缺陷,只是解释这段代码的唯一方法。

第三,你把事情弄得太复杂了,因为 Exit.t 不必首先成为 GADT 就可以实现你的目标。

下面是一个更简单的示例,表明如果 Nothing.t 实际上为空,则编译器正确地允许不相关的分支。

type nothing = |
type ('a, 'b) result =
  | Success of 'a
  | Failure of 'b
let only_success (x : ('a, nothing) result) : 'a =
  match x with
  | Success v -> v
  | Failure _ -> . (* this branch can be removed, as it is correctly inferred *)