我正在尝试将MonadError与Parsec一起使用。我想出了以下代码片段:
f5 = do
char 'a'
throwError "SomeError"
f6 = f5 `catchError` (\e -> unexpected $ "Got the error: " ++ e)
ret = runErrorT (runParserT f6 () "stdin" "a")
但是,ret
是Left "SomeError"
,似乎catchError没有任何效果。在这里使用MonadError的正确方法是什么?
我更喜欢使用MonadError而不是Parsec自己的错误处理,例如当我有:
try (many1 parser1) <|> parser2
如果parser1在这里失败,解析器2将继续,但我希望有一个例外,完全中止解析。
答案 0 :(得分:4)
我的印象是你试图让MonadError
出于错误的原因。
在try (many1 parser1) <|> parser2
中,您尝试避免的行为源于使用try
和<|>
- 如果您不喜欢它,请使用不同的组合器。也许像(many1 parser1) >> parser2
这样的表达对你来说会更好吗? (这会丢弃(many1 parser1)
的结果;您当然可以使用>>=
并将(many1 parser1)
的结果与来自parser2
的结果合并。)
(注意:在这一点上,对于手头的问题没有真正好的解决方案,只是为什么有些事情可能不起作用的一些思考...希望这可能(有点)启发,但不要期望太多。)
仔细研究ParsecT / MonadError交互。我担心这有点凌乱,我仍然不确定如何最好地去做OP想要做的事情,但我希望以下内容至少可以让我们深入了解缺乏成功的原因。最初的方法。
首先,请注意说Parsec是MonadError的实例是不正确的。当内部monad是Identity时,Parsec是由ParsecT生成的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)
接下来,让我们自己找一个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
monad中,代替通常的Identity
monad。由于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
生成一个值,所以当然它将(可用作)解析器...而且我恐怕不会看看如何将其打造成有助于打破解析过程的东西。
答案 1 :(得分:2)
如果您尝试调试解析器进行故障排除,则使用error
,Debug.Trace
或其他内容可能更为简单。
另一方面,如果你需要在某些输入上终止解析作为实际程序的一部分,但由于try (...) <|>
构造它没有这样做,那么你的 bug就会出现逻辑你应该停下来重新考虑你的语法,而不是通过错误处理来解决它。
如果您希望解析器在某些时间终止于给定输入,而不是其他输入,那么输入流中缺少某些内容(并且应该添加),或者解析器不是您问题的解决方案。 / p>
如果您希望解析器从非致命错误中正常恢复并在可能的情况下继续尝试,但在无法继续时以错误终止,那么您......可能想要考虑除Parsec以外的其他内容,因为它是真的不是为此设计的。我相信乌得勒支大学的Haskell解析器组合器库更容易支持这种逻辑。
编辑:就Parsec本身而言MonadError
的实例而言 - 是的,并且它自己的错误处理包含该功能。你要做的是在Parsec上堆叠一个第二个错误monad,你可能遇到了麻烦,因为通常很难区分那种“冗余”的monad变换器。处理多个状态monad更加着名的尴尬,这就是为什么Parsec(状态monad)提供保持自定义状态的功能。
换句话说,Parsec是一个错误monad对你没有任何帮助,事实上它主要与你的问题更加困难有关。
答案 2 :(得分:2)
如果您需要在某些输入上终止解析作为实际程序的一部分,但由于尝试(...)&lt; |&gt;而不是这样做构造,然后你的逻辑中有一个错误,你应该停下来重新思考你的语法,而不是通过错误处理来解决它。
如果你希望解析器在某些时候终止在给定的输入上,而不是其他的,那么输入流中缺少某些东西(并且应该被添加)或解析器不是你的解决方案问题。
这个答案基于假设问题在于语法。但是,如果我使用语法来提供编译器,那么语法无法处理其他错误。让我们说一个变量引用,一个未定义的变量。并且语言被指定为单个传递,并且变量被评估为遇到。然后,语法就好了。解析就好了。但是,由于评估了语法中指定的内容,发生了错误,现有的“失败”或“意外”或不足以处理此问题。如果能够在不诉诸更高级错误处理的情况下中止解析,那将是一件好事。