I'm trying to use the technique in Composable Error Handling in OCaml (Result type with polymorphic variants for errors) for some code I've written. The types of the functions I'm trying to use look like this:
val parse : parser -> token list -> (Nominal.term, [> `ParseError of string ]) Result.t
val lex : lexer -> string -> (token list, [> `LexError of string ]) Result.t
My attempt at composing them is this:
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
Unfortunately the compiler (4.09.0) reports a type error:
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
Note that if I do the equivalent by hand, the code compiles:
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))
Actually, that's not quite true. The equivalent is this, which also fails to compile (in the same way):
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
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 ]?
Also:
- What's the difference between my attempt and Vladimir's original (which I assume compiles)?
- 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)
Edit:
I uploaded all of my code for context.
Edit 2 (sorry):
I did some exploration and came up with this implementation:
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")
If I remove either of the commented lines, the implementation of lex_and_parse starts to fail again. It's a bit disturbing to me that parse compiles and its type signature never changes, yet a caller can fail to typecheck. How is this possible? This kind of nonlocal effect seriously violates my expectation for how type checking / signatures (ought to) work. I'd really like to understand what's happening.