3

I'm trying to use MonadError together with Parsec. I've come up with the following code snippet:

f5 = do
    char 'a'
    throwError "SomeError"

f6 = f5 `catchError` (\e -> unexpected $ "Got the error: " ++ e)

ret = runErrorT (runParserT f6 () "stdin" "a")

However, ret is Left "SomeError", it seems the catchError doesn't have any effect. What's the right way to use MonadError here?

I'd prefer to use MonadError over Parsec's own error handling, as for example when I have:

try (many1 parser1) <|> parser2

If parser1 fails here, parser2 will continue, but I'd like to have an exception which aborts the parsing entirely.

4

3 回答 3

5

我的印象是您MonadError出于错误的原因试图参与其中。

在 中try (many1 parser1) <|> parser2,您试图避免的行为源于使用tryand <|>-- 如果您不喜欢它,请使用不同的组合符。也许像(many1 parser1) >> parser2这样的表达更适合你?(这会丢弃来自 的结果(many1 parser1);您当然可以使用>>=来自 的结果并将其(many1 parser1)与来自的结果相结合parser2。)


(注意:在这一点之下,手头的问题并没有真正好的解决方案,只是对为什么有些事情可能不起作用的一些思考......希望这可能(有点)有启发性,但也不要指望很多。)

仔细检查 ParsecT / MonadError 交互。恐怕这有点混乱,我仍然不确定如何最好地去做 OP 想做的事情,但我希望以下内容至少可以深入了解缺乏成功的原因原来的做法。

首先,请注意说 Parsec 是 MonadError 的一个实例是不正确的。Parsec 是当内部单子是 Identity 时 ParsecT 产生的单子;ParsecT 产生 MonadError 的实例当且仅当它被赋予一个内部 monad 时,它本身就是一个 MonadError 的实例。GHCi相互作用的相关片段:

> :i Parsec
type Parsec s u = ParsecT s u Identity
    -- Defined in Text.Parsec.Prim
-- no MonadError instance

instance (MonadError e m) => MonadError e (ParsecT s u m)
  -- Defined in Text.Parsec.Prim
-- this explains why the above is the case
-- (a ParsecT-created monad will only become an instance of MonadError through
-- this instance, unless of course the user provides a custom declaration)

接下来,让我们自己做一个带有 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 Stringmonad 中,而不是通常的Identitymonad。因为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,所以它当然会(用作)一个解析器......而且我担心我不知道如何将它锤炼成一些有用的东西来打破解析过程。

于 2010-02-02T18:43:12.643 回答
2

if you need to terminate the parsing on some inputs as part of your actual program, but it's not doing so because of a try (...) <|> construct, then you have a bug in your logic and you should stop and rethink your grammar, rather than hack around it with error handling.

If you want the parser to terminate on a given input some of the time, but not others, then either something is missing from your input stream (and should be added) or a parser is not the solution to your problem.

This answer is based on the assumption that the problem lies in the grammar. But if I'm using the grammar to feed a compiler, there are other errors that a grammar can't handle. Let's say a variable reference, to a variable that wasn't defined. And the language is specified as a single pass, and variables are evaluated as encountered. Then, the grammar is just fine. The parsing is just fine. But as a result of evaluating what was specified in the grammar an error has occurred, the existing "fail" or "unexpected" or insufficient to deal with this problem. It would be nice to have a means to abort the parsing without resorting to higher level error handling.

于 2010-04-23T17:55:53.567 回答
2

If you're trying to debug a parser to troubleshoot, it's probably simpler to use error, Debug.Trace, or whatnot.

On the other hand, if you need to terminate the parsing on some inputs as part of your actual program, but it's not doing so because of a try (...) <|> construct, then you have a bug in your logic and you should stop and rethink your grammar, rather than hack around it with error handling.

If you want the parser to terminate on a given input some of the time, but not others, then either something is missing from your input stream (and should be added) or a parser is not the solution to your problem.

If you want the parser to recover gracefully from non-fatal errors and keep trying when possible, but terminate with an error when it can't continue, then you... may want to consider something other than Parsec, because it's really not designed for that. I believe Utrecht University's Haskell parser combinator library supports that sort of logic much more easily.

Edit: As far as Parsec being itself an instance of MonadError goes--yes, and its own error handling subsumes that functionality. What you're trying to do is stack a second error monad on top of Parsec, and you're probably having trouble because it's generally awkward to distinguish between monad transformers that are "redundant" in that manner. Dealing with multiple State monads is more famously awkward, which is why Parsec (a State monad as well) provides functionality to hold custom state.

In other words, Parsec being an error monad doesn't help you at all, and in fact is relevant mostly in the sense of making your problem more difficult.

于 2010-02-02T20:30:25.720 回答