在多态复合类型中使用GADT变体,就像我在正常代数变体类型中一样吗?

时间:2018-11-20 23:26:23

标签: polymorphism ocaml algebraic-data-types gadt

假设我有这个简单的变体类型:

type flag = {
  name: string;
  payload: string option;
}

type word =
 | Arg of string
 | Flag of flag

let args = [|
  Arg "hello";
  Flag {name = "foo"; payload = Some "world"};
|]

但是,如果我想将GADT约束添加到该word类型,

type _ word =
 | Arg : string -> string word
 | Flag : flag -> flag word

…编译器无法为args的成员推断出通用类型:

Line 12, 3:
  This expression has type flag word
       but an expression was expected of type string word
       Type flag is not compatible with type string

这仅仅是GADT的限制吗?

3 个答案:

答案 0 :(得分:2)

我当然不是GADT的专家,但这似乎比GADT更像是普通类型变量的限制。 flag wordstring word是不同的类型,无论如何都不会放入同一数组。

我假设您想要的是一个现有的量化类型变量。如果是这样,则存在类型变量应仅出现在箭头的左侧。这似乎可行:

type word =
 | Arg : string -> word
 | Flag : flag -> word

或者用the manual的话:

  

当变量出现在构造函数的参数中但不在其返回类型中时,它们就变为存在的。


编辑:不,正如@octachron在注释中指出的,这不是存在类型的工作方式。或更确切地说,是存在型变量,在我的示例中没有。原因是相同的,但我不清楚在这里需要GADT的什么属性以及解决方案是什么。

答案 1 :(得分:1)

GADT的局限性与其所需特性之一无关。撰写

type _ word =
| Arg : string -> string word
| Flag : flag -> flag word

您要让类型检查器使Arg _Flag _的类型不同且不兼容。

为激发这种行为,一个更好的示例可能是具有静态长度的列表:

 type zero = Zero
 type 'a succ = Succ
 type ('elt,'size) nlist =
 | []: ('elt, zero) nlist
 | (::): 'elt * ('elt, 'size) nlist ->  ('elt, 'size succ) nlist

使用此定义,类型('n, 's) nlist的值在此类型内携带其长度的编码。这样就可以编写总计hd函数

  let hd (a::q) = a

由于我们的奇异列表类型具有其类型的长度,所以类型检查器可以表示以下事实:hd仅接受带有一个或多个参数的列表(即hd的类型为 ('elt,_ succ) nlist -> 'elt)。因此,函数hd总是返回(在检查类型时)。

但这也意味着类型检查器现在必须强制执行,而功能hd始终可以起作用。换句话说,不同大小的数组混合长度

  [| []; [1]; [1;2] |]

不允许进行类型检查,因为它包含未明确定义hd函数的元素,并且保证在hd之前的类型检查器将始终返回成功的值。 / p>

回到您的示例,使用类型定义,您可以区分Flag _Int _。因此,我可以编写以下总函数

 let empty (Arg _) = ""
 let map_empty = Array.map empty

,并且期望map_empty适用于所有类型良好的数组。但是我可能无法将此功能应用于您的混合数组

 let args = [|
   Arg "hello";
   Flag {name = "foo"; payload = Some "world"};
 |]

换句话说,该数组的类型不能正确。

答案 2 :(得分:0)

使用GADT处理这种情况的通常方法是引入存在性包装器(此处为any_word):

type flag = {
  name : string;
  payload : string option;
}

type _ word =
  | Arg : string -> string word
  | Flag : flag -> flag word

type any_word = Word : _ word -> any_word [@@unboxed]

let args = [|
  Word (Arg "hello");
  Word (Flag {name = "foo"; payload = Some "world"});
|]

args包含未知类型索引的word个,该索引隐藏在any_word包装器中。相反,

let flag_args = [|
  Flag {name = "zonk"; payload = None};
  Flag {name = "splines"; payload = Some "reticulate"};
|]

flag_args具体包含flag word个。 string word被类型系统排除,这是GADT的吸引力之一。 (如果您不需要这样做,则GADT可能不会有所帮助。)