将 MonadError 与 Parsec 结合使用

Joh*_*ner 3 monads haskell monad-transformers

我正在尝试将 MonadError 与 Parsec 一起使用。我想出了以下代码片段:

f5 = do
    char 'a'
    throwError "SomeError"

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

ret = runErrorT (runParserT f6 () "stdin" "a")
Run Code Online (Sandbox Code Playgroud)

但是,retis Left "SomeError",似乎 catchError 没有任何效果。在这里使用 MonadError 的正确方法是什么?

我更喜欢使用 MonadError 而不是 Parsec 自己的错误处理,例如当我有:

try (many1 parser1) <|> parser2
Run Code Online (Sandbox Code Playgroud)

如果 parser1 在这里失败, parser2 将继续,但我希望有一个完全中止解析的例外。

Mic*_*zyk 5

我的印象是你试图以MonadError错误的理由参与进来。

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


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

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

首先,请注意,说 Parsec 是 MonadError 的实例是不正确的。Parsec 是 ParsecT 在内部 monad 为 Identity 时产生的 monad;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)
Run Code Online (Sandbox Code Playgroud)

接下来,让我们自己创建一个带有 catchError 和 ParsecT 的工作示例。考虑这个 GHCi 交互:

> (runParserT (char 'a' >> throwError "some error") () "asdf" "a" :: Either String (Either ParseError Char)) `catchError` (\e -> Right . Right $ 'z')
Right (Right 'z')
Run Code Online (Sandbox Code Playgroud)

类型注释似乎是必要的(这对我来说似乎很直观,但它与原始问题无关,因此我不会尝试详细说明)。整个表达式的类型由 GHC 确定如下:

Either String (Either ParseError Char)
Run Code Online (Sandbox Code Playgroud)

所以,我们有一个常规的解析结果 -- Either ParseError Char-- 包裹在Either Stringmonad 中,而不是通常的Identitymonad。由于Either String是 的实例MonadError,我们可以使用throwError/ catchError,但传递给的处理程序catchError当然必须产生正确类型的值。恐怕这对于打破解析例程并不是很有用。

回到问题中的示例代码。那做的事情略有不同。让我们检查ret问题中定义的类型:

forall (m :: * -> *) a.
(Monad m) =>
m (Either [Char] (Either ParseError a))
Run Code Online (Sandbox Code Playgroud)

(根据 GHCi ... 请注意,我必须解除单态限制,{-# LANGUAGE NoMonomorphismRestriction #-}才能在没有类型注释的情况下编译代码。)

这种类型暗示了可以用 做一些有趣的事情ret。开始了:

> runParserT ret () "asdf" "a"
Right (Left "some error")
Run Code Online (Sandbox Code Playgroud)

事后看来,给定的处理程序catchError使用 产生一个值unexpected,所以当然它会(可用作)一个解析器......而且我恐怕我不知道如何把它变成有用的东西来打破解析过程。