我试图在我编写的一些代码中使用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
的两个地方都有>
约束。那么这种类型是从哪里来的呢?[>
ParseError of string become
‘ParseError of string’?
另外:
[ x ]
到[> x ]
的多态变体?(除了手动将所有标记从第一种类型映射到第二种类型之外)编辑:
I 上传了我所有的代码表示上下文。
编辑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
编译并且它的类型签名从未改变,但是调用者可能会失败。这怎麽可能?这种非局部效应严重地违背了我对类型检查/签名(应该)工作方式的期望。我真的很想了解发生了什么。
发布于 2020-06-02 16:59:56
因此,首先,您的直觉是正确的,您的代码应该工作,例如,以下代码被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 )结果”。这就是我不明白的地方--我从来没有在任何地方指定过这种类型,事实上,提到ParseError的两个地方都有一个>约束。那么这种类型是从哪里来的呢?字符串的解析在哪里变成字符串的ParseError?
你说得对,这是罪魁祸首。由于某些原因,parse
函数返回一个类型的值。
(term, [`ParseError of string])
如果错误成分的类型是基本类型,即它不是多态的,不能扩展。很难说为什么会发生这种情况,但我敢打赌,应该有一些类型注释可以阻止类型检查器推断出parse
函数的最一般类型。在任何情况下,罪魁祸首都藏在某个地方,而不是你展示给我们的代码中。
有没有办法削弱多态变量从x到>x?(除了手动将所有标记从第一种类型映射到第二种类型之外)
是,
# let weaken x = (x : [`T] :> [> `T]);;
val weaken : [ `T ] -> [> `T ] = <fun>
我的尝试和Vladimir的原版有什么区别(我认为是编译的)?
实际上,解析函数返回一个不可扩展的类型。请注意,要将不可扩展类型转换为可扩展类型,必须使用完整的表单强制,例如,如果要将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
函数这里限制为返回不可扩展的地面类型错误的错误。
现在来缓解。
最简单的解决方案是删除类型注释,并在编程时依赖类型检查器、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
]
这两种定义是等价的。这一切意味着类型'a parser_error
是一个类型变量'a
s.t。'a
包括ParseError、MoonPhaseError和无限多个未指定属的误差。
发布于 2020-06-02 16:39:12
下列功能代码:
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
的预期类型编译很好。
因此,问题可能是parse
和lex
的实现有一个封闭的错误类型。
请注意,此问题可以很容易地通过强制解决,因为关闭错误类型是打开错误类型的子类型:
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
。因此,当在result
中返回parse
时,它的类型是(_,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之前引起混淆)。
然后,一个可能的解决方法是添加强制以重新打开解析中的错误类型:
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 ... =
如果没有类型注释,编译器将推断出正确的类型。实际上,这是一种相当普遍的情况:保证编译器在不受用户干扰的情况下推断出最佳类型。
https://stackoverflow.com/questions/62155850
复制相似问题