多态变体和 let%bind 类型错误

Polymorphic variants and let%bind type error

我正在尝试将 Composable Error Handling in OCaml 中的技术(错误的多态变体的结果类型)用于我编写的一些代码。我尝试使用的函数类型如下所示:

val parse : parser -> token list -> (Nominal.term, [> `ParseError of string ]) Result.t
val lex : lexer -> string -> (token list, [> `LexError of string ]) Result.t

我的创作尝试是这样的:

let lex_and_parse
  : parser -> lexer -> string -> (Nominal.term, [> `ParseError of string | `LexError of string ]) Result.t
  = fun parser lexer input ->
    let open Result.Let_syntax in
    let%bind tokens = lex lexer input in
    parse parser tokens

不幸的是,编译器 (4.09.0) 报告类型错误:

File "src/Pratt.ml", line 147, characters 4-23:
147 |     parse parser tokens
          ^^^^^^^^^^^^^^^^^^^
Error: This expression has type
         (Nominal.term, [ `ParseError of string ]) result
       but an expression was expected of type
         (Nominal.term, [> `LexError of string ]) result
       The first variant type does not allow tag(s) `LexError

请注意,如果我手动执行等效操作,代码会编译:

let lex_and_parse
  : parser -> lexer -> string -> (Nominal.term, [> `ParseError of string | `LexError of string ]) Result.t
  = fun parser lexer input ->
    match lex lexer input with
      | Error (`LexError err) -> Error (`LexError err)
      | Ok tokens ->
        (match parse parser tokens with
        | Ok result -> Ok result
        | Error (`ParseError err) -> Error (`ParseError err))

实际上,事实并非如此。等效的是这个,它也无法编译(以相同的方式):

    match lex lexer input with
      | Error err -> Error err
      | Ok tokens ->
        match parse parser tokens with
        | Ok result -> Ok result
        | Error err -> Error err
File "src/Pratt.ml", line 155, characters 29-32:
155 |         | Error err -> Error err
                                   ^^^
Error: This expression has type [ `ParseError of string ]
       but an expression was expected of type
         [> `LexError of string | `ParseError of string ]
       The first variant type does not allow tag(s) `LexError

所以我的问题是这样的。请注意,错误消息显示 "This expression has type (Nominal.term, [ `ParseError of string ]) result"。这是我不明白的——我从未在任何地方指定该类型,事实上,两个地方都提到了 ParseError,它带有 > 约束。那么这种类型是从哪里来的呢? IE 在哪里 [>ParseError of string ]become[ ParseError of string ]?

还有:

编辑:

uploaded all of my code 的上下文。

编辑 2(抱歉):

我做了一些探索并提出了这个实现:

let parse : parser -> token list -> (Nominal.term, [> `ParseError of string ]) Result.t
  = fun parser toks ->
    match expression parser toks with
    (* | [], result -> result *)
    (* | _, Error err -> Error err *)
    | _, Ok _ -> Error (`ParseError "leftover tokens")
    | _, _ -> Error (`ParseError "unimplemented")

如果我删除 任一个 注释行,lex_and_parse 的实施将再次失败。 parse 编译并且它的类型签名永远不会改变,但调用者可能无法进行类型检查,这让我有点不安。这怎么可能?这种非局部效应严重违反了我对类型检查/签名(应该)如何工作的期望。我真的很想了解发生了什么。

以下函数化代码:

module F(X: sig
    type parser type lexer type token
    module Nominal: sig type term end
    val parse :
      parser -> token list -> (Nominal.term, [> `ParseError of string ]) Result.t
    val lex : lexer -> string -> (token list, [> `LexError of string ]) Result.t
end) = struct
open X

let (let*) x f = match x with
  | Error _ as e -> e
  | Ok x -> f x

let lex_and_parse parser lexer input =
  let* tokens = lex lexer input in
  parse parser tokens
end

lex_and_parse.

的预期类型编译得很好

因此,问题可能是您对 parselex 的实施具有封闭的错误类型。

请注意,由于封闭错误类型是开放错误类型的子类型,因此可以通过强制转换轻松解决此问题:

let fix parse =
(parse:
    parser -> token list -> (Nominal.term, [`ParseError of string ]) Result.t
 :> parser -> token list -> (Nominal.term, [>`ParseError of string ]) Result.t
 )

但最好修复相应功能的实现。

编辑:

初始错误来自您的这部分代码:

type parse_error = [ `ParseError of string ]
type parse_result = (Nominal.term, parse_error) Result.t
...
let rec expression
  : parser -> ?rbp:int -> token list -> token list * parse_result
...
let parse :
  parser -> token list -> (Nominal.term, [> `ParseError of string ]) Result.t
  = fun parser toks -> match expression parser toks with
    | [], result -> result

此处您将 ​​parse_error 类型中的错误限制为恰好 `Parse_error。因此,当您在 parse 中 return result 时,它的类型是 (_,parse_error) result。而且由于这个结果类型可以和你的注解统一,所以不会报错。

也许第一个修复方法是详细说明类型,让类型检查器知道您打算打开错误类型:

let parse : 'error.
  parser -> token list -> 
  (Nominal.term, [> `ParseError of string ] as 'error) Result.t
  = fun parser toks -> match expression parser toks with
    | [], result -> result

在这里,return 类型上的显式通用注释将防止该类型在您背后被关闭(错误消息在 4.10 之前可能会令人困惑)。

然后,一种可能的解决方法是添加强制以重新打开解析中的错误类型:

let parse : 'error.
  parser -> token list ->
  (Nominal.term, [> `ParseError of string ] as 'error) Result.t
  = fun parser toks -> match expression parser toks with
    | [], result -> (result:>(Nominal.term, [> parse_error]) result)

或者你也可以打开错误类型的表达式:

type parse_error = [ `ParseError of string ]
type 'a parse_result = (Nominal.term, [> parse_error] as 'a) Result.t
...
let rec expression
  : parser -> ?rbp:int -> token list -> token list * 'a parse_result
  = ...

或者更简单的解决方法:删除类型注释,

let rec expression parser ... =

没有类型注释,编译器会推断出正确的类型。事实上,这是一种非常普遍的情况:编译器保证在没有用户干扰的情况下推断出最佳类型。

所以首先,您的直觉是正确的,您的代码应该可以工作,例如,以下代码被 4.09.0 接受而没有任何类型错误:

open Base

module type S = sig
  type parser
  type lexer
  type token
  type term

  val parse : parser -> token list -> (term, [> `ParseError of string ]) Result.t
  val lex : lexer -> string -> (token list, [> `LexError of string ]) Result.t
end

module Test (P : S) = struct
  open P
  let lex_and_parse :
    parser -> lexer -> string -> (term, [> `ParseError of string | `LexError of string ]) Result.t
    = fun parser lexer input ->
      let open Result.Let_syntax in
      let%bind tokens = lex lexer input in
      parse parser tokens
end


module PL : S = struct
  type parser
  type lexer
  type token
  type term

  let parse _parser _tokens = Error (`ParseError "not implemented")
  let lex _ _ = Error (`LexError "not implemented")
end

So my question is this. Note that the error message says "This expression has type (Nominal.term, [ `ParseError of string ]) result". This is what I don't understand -- I never specify that type anywhere, in fact, both places ParseError is mentioned, it's with a > constraint. So where does this type come from? IE where does [>ParseError of string ]become[ ParseError of string ]?

你说得对,这才是罪魁祸首。出于某种原因,您的 parse 函数 return 是

类型的值
(term, [`ParseError of string])

其中错误成分的类型是基础类型,即它不是多态的,不能扩展。很难说为什么会发生这种情况,但我敢打赌,应该有一些类型注释可以阻止类型检查器为 parse 函数推断出最通用的类​​型。无论如何,罪魁祸首隐藏在某个地方,而不是您向我们展示的代码中。

Is there a way to weaken a polymorphic variant from [ x ] to [> x ]? (other than mapping all the tags by hand from the first type to the second)

是的,

# let weaken x = (x : [`T]  :> [> `T]);;
val weaken : [ `T ] -> [> `T ] = <fun>

What's the difference between my attempt and Vladimir's original (which I assume compiles)?

您的解析函数实际上 return 是不可扩展的类型。请注意,要将不可扩展类型转换为可扩展类型,您必须使用完整形式的强制转换,例如,如果您将 lex_and_parse 定义为

  let lex_and_parse :
    parser -> lexer -> string -> (term, [> `ParseError of string | `LexError of string ]) Result.t
    = fun parser lexer input ->
      let open Result.Let_syntax in
      let parse = (parse
                   :  _ -> _ -> (_, [ `ParseError of string]) Result.t
                   :> _ -> _ -> (_, [> `ParseError of string]) Result.t) in

      let%bind tokens = lex lexer input in
      parse parser tokens

它将编译。但罪魁祸首还是你的 parse 函数的类型。

实际错误隐藏的地方

在 OP 上传源代码后,我们能够确定 OCaml 类型检查器被拒绝推断通用类型和多态类型的原因和位置。

故事是这样的,parse函数实现为

let parse : parser -> token list -> (Nominal.term, [> `ParseError of string ]) Result.t
  = fun parser toks -> match expression parser toks with
    | [], result -> result
    | _, Ok _ -> Error (`ParseError "leftover tokens")
    | _, Error err -> Error err

所以它的return类型是以下表达式类型的统一:

  • result
  • Error ('ParseError "leftover tokens")
  • Error err

另外我们还有一个类型约束

parser -> token list -> (Nominal.term, [> `ParseError of string ]) Result.t

这里有一件重要的事情要理解,类型约束不是定义,所以当你说 let x : 'a = 42 你没有定义 x 具有通用多态类型 'a.类型约束 (expr : typeexpr) 强制 expr 的类型与 typexpr 兼容。也就是说,类型约束只能约束类型,而类型本身总是由类型检查器推断出来的。如果推断类型比约束更通用,例如 'a list,例如 int list,那么它将被约束为 int list。但是你不能反过来,因为它会破坏类型的可靠性,例如,如果推断类型是 int list 而你的约束是 'a list,那么它仍然是 'a list (将其视为类型的交集)。同样,类型推断将推断出最通用的类​​型,您只能使其不那么通用。

所以,最后,parse子程序的return类型是上面三个表达式加上用户约束的统一结果。 result 的类型是最小的类型,因为你已经约束了 expression 函数 here to return errors of the non-extensible ground type parse_error.

现在开始缓解措施。

最简单的解决方案是在编程时完全删除类型注释并依赖类型检查器、merlin 和定义明确的接口(签名)。实际上,类型注解只会让您在这里感到困惑。您编写了可扩展的 [> ...] 类型注释,并认为推断的类型是可扩展的,但事实并非如此。

如果您需要保留它们或者如果您需要使表达式函数成为您界面的一部分,那么您有两个选择,要么使您的 parse_error 可扩展,这意味着多态或使用类型强制弱化结果类型并使其可扩展,例如

| [], result -> (result : parse_error :> [> parse_error])

如果您决定让您的 parse_error 类型可扩展,您不能只说

type parse_error = [> `ParseError of string]

因为现在parse_error表示一整套类型,所以我们需要用类型变量来表示这种类型的可变性,这里有两种语法是适用的,

type 'a parse_error = [>
  | `ParseError of string
  | `MoonPhaseError of int
] as 'a

或更冗长,但对我来说更精确,

type 'a parse_error = 'a constraint 'a = [>
    | `ParseError of string
    | `MoonPhaseError of int
  ]

这两个定义是等价的。 all 表示类型 'a parser_error 是类型变量 'a s.t。 'a 包括 ParseError、MoonPhaseError 和无限多的未指定属的错误。