0

对于我编写的一些代码,我正在尝试使用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

所以我的问题是这个。请注意,错误消息显示“此表达式具有类型(Nominal.term, [ `ParseError of string ]) result”。这是我不明白的——我从来没有在任何地方指定那种类型,事实上,这两个地方ParseError都提到了,它有一个>约束。那么这种类型是从哪里来的呢?IE [>ParseError of string ] become[在ParseError of string ]哪里?

还:

  • 我的尝试和 Vladimir 的原版(我假设它是编译的)有什么区别?
  • 有没有办法将多态变体从[ x ]to弱化[> x ]?(除了手动将所有标签从第一种类型映射到第二种类型)

编辑:

上传了所有的上下文代码。

编辑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,但是调用者可能无法进行类型检查。这怎么可能?这种非局部效应严重违反了我对类型检查/签名(应该)如何工作的期望。我真的很想了解发生了什么。

4

2 回答 2

3

所以首先,你的直觉是正确的,你的代码应该可以工作,例如,以下代码被 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

所以我的问题是这个。请注意,错误消息显示“此表达式具有类型 (Nominal.term, [ `ParseError of string ]) result”。这是我不明白的——我从来没有在任何地方指定该类型,事实上,ParseError 两个地方都提到过,它带有 > 约束。那么这种类型是从哪里来的呢?IE [>ParseError of string ] 变成 [ParseError of string ] 在哪里?

你是对的,这是罪魁祸首。出于某种原因,您的parse函数返回一个类型的值

(term, [`ParseError of string])

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

有没有办法削弱从 [ x ] 到 [> x ] 的多态变体?(除了手动将所有标签从第一种类型映射到第二种类型)

是的,

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

我的尝试和 Vladimir 的原版(我假设它是编译的)有什么区别?

您的 parse 函数实际上返回一个不可扩展的类型。请注意,要将不可扩展类型转换为可扩展类型,您必须使用完整形式的强制转换,例如,如果您将定义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

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

  • 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子程序的返回类型是上面三个表达式加用户约束的统一结果。的类型result是最小的类型,因为您已在此处expression限制函数返回不可扩展的基本类型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
  ]

两个定义是等价的。都意味着 type'a parser_error是一个类型变量'ast'a包括 ParseError、MoonPhaseError 和无限更多的未指定属的错误。

于 2020-06-02T16:59:56.017 回答
2

以下函数化代码:

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。因此,当您返回resultparse,它的类型是(_,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

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

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

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 ... =

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

于 2020-06-02T16:39:12.027 回答