繁体   English   中英

当 GADT 构造函数包含多个类型变量时,本地抽象类型出现 Scope 错误

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM