【问题标题】:Using MonadError with Parsec将 MonadError 与 Parsec 一起使用
【发布时间】:2010-02-02 18:10:00
【问题描述】:

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

f5 = do
    char 'a'
    throwError "SomeError"

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

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

但是,retLeft "SomeError",看来 catchError 没有任何作用。在这里使用 MonadError 的正确方法是什么?

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

try (many1 parser1) <|> parser2

如果 parser1 在此处失败,则 parser2 将继续,但我希望有一个异常完全中止解析。

【问题讨论】:

    标签: haskell monads monad-transformers


    【解决方案1】:

    我的印象是你试图让 MonadError 参与进来是出于错误的原因。

    try (many1 parser1) &lt;|&gt; parser2 中,您试图避免的行为源于对try&lt;|&gt; 的使用——如果您不喜欢它,请使用不同的组合符。也许像(many1 parser1) &gt;&gt; parser2 这样的表达式更适合您? (这会丢弃来自(many1 parser1) 的结果;您当然可以使用&gt;&gt;= 并将来自(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 String monad 代替了通常的Identity monad。由于Either StringMonadError 的一个实例,我们可以使用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 生成一个值,所以它当然会(用作)解析器......而且我恐怕我不知道如何锤击这对打破解析过程很有用。

    【讨论】:

    • 我想在某个任意点中止解析过程,“try”只是一个例子。
    • 为了快速和肮脏的调试目的,你总是可以在链中插入类似error "message"的东西......比如说try (many1 parser1) &lt;|&gt; error "kaboom!" &lt;|&gt; parser2。但是,这确实会使您的程序崩溃,因此如果您想在记录错误条件信息的同时处理一堆字符串,这是不可接受的......我会看看我是否能在这种情况下做得更好。
    • 好吧,我不确定这是否会对您有所帮助,但我已经尝试分析正在发生的事情并将调查结果编辑为答案......如果这有帮助,很好,如果不,让我知道,我可能会删除整个内容。不过,Parsec 实际上并不是 MonadError 的一个实例(与其他一些可以用 ParsecT 构造的 monad 不同)这一点非常重要。
    【解决方案2】:

    如果您尝试调试解析器进行故障排除,使用errorDebug.Trace 或诸如此类可能更简单。

    另一方面,如果您需要终止对某些输入的解析作为实际程序的一部分,但由于 try (...) &lt;|&gt; 构造而没有这样做,那么您的逻辑中有一个 错误 你应该停下来重新思考你的语法,而不是用错误处理来绕过它。

    如果您希望解析器在某些时候终止给定的输入,而不是其他时候,那么您的输入流中缺少某些内容(应该添加)或者解析器不是您问题的解决方案。

    如果您希望解析器从非致命错误中正常恢复并在可能的情况下继续尝试,但在无法继续时以错误终止,那么您...可能需要考虑 Parsec 以外的其他内容,因为它是真的不是为此而设计的。我相信乌得勒支大学的 Haskell 解析器组合库更容易支持这种逻辑。

    编辑:Parsec 本身就是MonadError 的一个实例——是的,它自己的错误处理包含了该功能。您尝试做的是在 Parsec 之上堆叠一个 second 错误 monad,您可能会遇到麻烦,因为通常很难区分以这种方式“冗余”的 monad 转换器。众所周知,处理多个 State monad 比较尴尬,这就是 Parsec(也是 State monad)提供保持自定义状态的功能的原因。

    换句话说,Parsec 是一个错误单子对你没有任何帮助,事实上,主要是在使你的问题变得更困难的意义上。

    【讨论】:

    • 为什么它不是为此而设计的?我的意思是,Parsec 是MonadError 的一个实例。我不想像你提到的那样做黑客,只是在某些情况下我想使用异常中止解析过程。老实说,我更感兴趣的是为什么我的代码不起作用,而不是为什么我根本不应该使用 MonadError。
    • 它不是为一般的错误恢复而设计的,因为这是一个非常难以解析的问题; Parsec 通常在以下假设下工作:解析器要么成功,要么立即无害地失败,要么不可恢复地失败(由于消耗输入)。 Parsec 自己的错误处理应该足以应对所有这些情况;如果您需要超出此范围,则 Parsec 可能不适合您的任务。
    【解决方案3】:

    如果您需要终止对某些输入的解析作为实际程序的一部分,但由于 try (...) 构造而没有这样做,那么您的逻辑中有错误你应该停下来重新思考你的语法,而不是用错误处理来绕过它。

    如果您希望解析器在某些时候终止给定的输入,而不是其他时候,那么您的输入流中缺少某些内容(应该添加)或者解析器不是您的解决方案问题。

    这个答案是基于问题出在语法上的假设。但是,如果我使用语法来为编译器提供数据,那么还有其他语法无法处理的错误。假设一个变量引用,指向一个未定义的变量。并且语言被指定为单遍,并且变量被评估为遇到。然后,语法就好了。解析就好了。但是作为评估语法中指定的内容的结果发生了错误,现有的“失败”或“意外”或不足以处理此问题。如果有一种方法可以在不求助于更高级别的错误处理的情况下中止解析,那就太好了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多