假设我有这个简单的变体类型:
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的限制吗?
答案 0 :(得分:2)
我当然不是GADT的专家,但这似乎比GADT更像是普通类型变量的限制。 flag word
和string 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可能不会有所帮助。)