[英]Scope error with locally abstract type when GADT constructor contains multiple type variables
鉴于此 GADT
type _ t =
| A : (string -> 'ok) * (string -> 'err) -> ('ok, 'err) result t
为什么这会失败并出现“类型构造函数将逃脱其范围”错误
let f (type a) (gadt: a t): a Future.t =
Future.bind (fetch ()) (fun response ->
match gadt with
| A (ok, _) -> Ok (ok response))
Error: This expression has type $0 but an expression was expected of type 'a
The type constructor $0 would escape its scope
而这只是将模式匹配提取到一个单独的 function 中,有效吗?
let parse (type a) (gadt: a t) (response: string): a =
match gadt with
| A (ok, _) -> Ok (ok response)
let f (type a) (gadt: a t): a Future.t =
Future.bind (fetch ()) (parse gadt)
第一个示例也适用于更简单的 GADT,例如
type _ t =
| B : (string -> 'a) -> 'a t
这表明 GADT 构造函数与多个类型变量、局部抽象类型和闭包之间存在一些奇怪的交互。
这已经在从 4.06 到 4.14 的几个 OCaml 版本上进行了测试,结果相同,所以它似乎至少不太可能是一个错误。
module Future = struct
type 'a t
let bind : 'a t -> ('a -> 'b) -> 'b t = Obj.magic
end
let fetch : unit -> 'a Future.t = Obj.magic
type _ t =
| A : (string -> 'ok) * (string -> 'err) -> ('ok, 'err) result t
let parse (type a) (gadt: a t) (response: string): a =
match gadt with
| A (ok, _) -> Ok (ok response)
let f_works (type a) (gadt: a t): a Future.t =
Future.bind (fetch ()) (parse gadt)
let f_fails (type a) (gadt: a t): a Future.t =
Future.bind (fetch ()) (fun response ->
match gadt with
| A (ok, _) -> Ok (ok response))
上面的例子有点过于简单了,虽然它确实很好地说明了潜在的问题,但它并没有表明我确实需要抽象整个('ok, 'err) result
类型,因为还有其他具有其他形状的构造函数,此处由额外的B
构造函数说明:
type _ t =
| A : (string -> 'ok) * (string -> 'err) -> ('ok, 'err) result t
| B : (string -> 'a) -> 'a t
let f (type a) (gadt: a t): a Future.t =
Future.bind (fetch ()) (fun response ->
match gadt with
| A (ok, _) -> Ok (ok response)
| B f -> f response)
在这种情况下查看问题的另一种方法是
let f (type a) (gadt: a t): a Future.t =
Future.bind (fetch ()) (fun response ->
match gadt with
| A (ok, _) -> Ok (ok response)
)
期望类型约束a Future.t
在正确的时间传播到bind
function 的参数,用于将类型($0,$1) result
重新打包a
.
这行不通。 最小的修复是在离开模式匹配时添加注释:
let f (type a) (gadt: a t) =
Future.bind (fetch ()) (fun response ->
match gadt with
| A (ok, _) -> (Ok (ok response): a)
)
通过这个注解,我们使用局部方程($0,$1) result=a
来确保局部类型$0
和$1
不会逃脱它们的模式匹配分支。
您必须为每个类型变量创建一个本地抽象类型以扩展其 scope,例如,以下类型检查,
let f (type ok err) (gadt: (ok,err) result t): (ok,err) result Future.t =
Future.bind (fetch ()) (fun response ->
match gadt with
| A (ok, _) -> Ok (ok response))
在您的示例中,您绑定a
with ('ok,'err) result
,编译器假设如果a
本地抽象,那么'ok
和'err
也是抽象的,这有点牵强。 换句话说,你不能用一个变量抽象出两个变量。
此外,与 scope 问题无关,当您将'a
in 'at
抽象为a
,然后使用Ok (ok response)
将其与_ result
统一时,您破坏了a
的抽象。
let f (type a) (gadt: a t): a Future.t =
Future.bind (fetch ()) (fun response ->
match gadt with
| A (ok, err) ->
Ok (ok response))
这由您决定忽略的错误消息指示,
Type ('a, 'b) result is not compatible with type a
'at
的类型变量在 GADT 的所有分支之间共享,并且在分支A
中被约束为('ok,'error) result
的事实并不意味着'at
的所有实例都被约束到它,即使你的类型definition 此时只有一个分支。
对于它的价值,虽然您的示例可能只是为了突出问题而进行的最小化,但是可以使用简单的 ADT 在没有任何 GADT 和 scope 问题的情况下实现相同的行为,例如,
type 'a t = {
ok : string -> 'ok;
err : string -> 'err;
} constraint 'a = ('ok,'err) result
let f {ok; err} =
Future.bind (fetch ()) (fun response ->
Ok (ok response))
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.