我认为你试图错误地使用MonadError
。
在try (many1 parser1) <|> parser2
中,你想避免的行为源于使用了try
和<|>
——如果你不喜欢它,可以使用不同的组合子。也许像(many1 parser1) >> parser2
这样的表达式更适合你?(这会丢弃(many1 parser1)
的结果;当然你可以使用>>=
将(many1 parser1)
的结果与parser2
的结果结合起来。)
(注意:在此之下,没有真正好的解决方案,只是一些对为什么某些事情可能行不通的思考...希望这可能会有所启发,但不要期望太多。)
对ParsecT / MonadError交互的更详细的研究。恐怕它有点混乱,我仍然不确定如何最好地做到OP想做的事情,但我希望以下内容至少能够提供有关原始方法缺乏成功的原因的见解。
首先,需要注意的是不能说Parsec是MonadError的实例。当内部单子为Identity时,Parsec是由ParsecT产生的单子;只有当ParsecT被赋予一个内部单子,并且该单子本身是MonadError的实例时,ParsecT才会产生MonadError的实例。以下是GHCi交互的相关片段:
> :i Parsec
type Parsec s u = ParsecT s u Identity
instance (MonadError e m) => MonadError e (ParsecT s u m)
接下来,让我们通过使用catchError和ParsecT来进行一个工作示例。考虑以下GHCi交互:
> (runParserT (char 'a' >> throwError "some error") () "asdf" "a" :: Either String (Either ParseError Char)) `catchError` (\e -> Right . Right $ 'z')
Right (Right 'z')
类型注释似乎是必需的(这对我来说很有直觉意义,但它与原始问题无关,所以我不会试图阐述)。 GHC 确定整个表达式的类型如下:
Either String (Either ParseError Char)
因此,我们得到了一个常规的解析结果——
Either ParseError Char
——用
Either String
单子替换了通常的
Identity
单子。由于
Either String
是
MonadError
的一个实例,我们可以使用
throwError
/
catchError
,但是传递给
catchError
的处理程序必须产生正确类型的值。对于中断解析过程来说,这并不是非常有用。
回到问题中的示例代码。它做的事情略有不同。让我们检查在问题中定义的
ret
的类型:
forall (m :: * -> *) a.
(Monad m) =>
m (Either [Char] (Either ParseError a))
根据GHCi的提示...请注意,我必须使用
{-# LANGUAGE NoMonomorphismRestriction #-}
来解除单态限制,以便代码在没有类型注释的情况下编译。
该类型是对使用ret
进行有趣操作的可能性的提示。我们开始吧:
> runParserT ret () "asdf" "a"
Right (Left "some error")
回过头来看,传递给 catchError
的处理程序使用 unexpected
生成了一个值,所以当然它将成为(可用作)解析器...但我担心我不知道如何将其转化为有用的内容,以便打破解析过程。
error "message"
的内容...比如说,try (many1 parser1) <|> error "kaboom!" <|> parser2
。然而,这会导致程序崩溃,因此如果您想在记录错误条件的同时处理一堆字符串,这种方法是不可接受的...我会看看是否有更好的解决方案。 - Michał Marczyk