了解三连胜解析器 <|> 并尝试

understanding trifecta parser <|> and try

在阅读 Haskell 本书时,我偶然发现了 trifecta

我正在绞尽脑汁还是无法理解<|>

我有以下问题。

简单来说 (<|>) = Monadic Choose ?

p = a <|> b -- 使用解析器 a 如果没有则使用 b ?

如果是那么为什么下面的解析器失败了?

parseFraction :: Parser Rational
parseFraction = do
    numerator <- decimal
    char '/'
    denominator <- decimal
    case denominator of 
        0 -> fail "denominator cannot be zero"
        _ -> return (numerator % denominator)


type RationalOrDecimal = Either Rational Integer
parseRationalOrDecimal = (Left <$> parseFraction) <|> (Right<$> decimal)


main = do
    let p f i = parseString f mempty i
    print $ p (some (skipMany (oneOf "\n")  *> parseRationalOrDecimal <* skipMany (oneOf "\n"))) "10"

在完美世界中,如果 parseFraction 会失败,那么 <|> 应该使用 decimal 但这不是案子。 但是当我使用时,它会起作用。

  1. 我错过了什么?
  2. 为什么我们需要使用 try 而 <|> 应该在第一次失败时 运行 第二个解析器?

parseRationalOrDecimal = try (Left <$> parseFraction) <|> (Right<$> decimal)

原因是因为parseFraction在失败前消耗了输入,因此,它被认为是选择中的正确分支。让我举个例子:

假设你正在编写一个 python 解析器,你必须决定声明是 class 还是函数(关键字 def),然后你写

parseExpresion = word "def" <|> word "class" -- DISCLAIMER: using a ficticious library

然后如果用户写defclass它会匹配,但是如果用户写det它会尝试第一个分支并匹配de然后未能匹配预期的 f,因为找到了 t。它不会费心尝试下一个解析器,因为错误被认为是在第一个分支中。尝试 class 解析器毫无意义,因为错误很可能在第一个分支中。

在你的例子中 parseFraction 匹配了一些数字然后失败了,因为 / 没有找到,然后它就懒得尝试 decimal 解析器了。

这是一个设计决定,其他一些库使用不同的约定(例如:Attoparsec 总是在失败时回溯),并且一些函数声称“不消耗输入”(例如:notFollowedBy )

注意这里有一个权衡:

首先:如果<|>的行为与您预期的一样

parse parseRationalOrDecimal "123456789A"

将首先解析所有数字,直到找到“A”,然后它将再次解析 所有数字,直到找到“A”...所以进行两次相同的计算只是 return 失败。

其次:如果您更关心错误消息,则当前行为更方便。按照 python 的例子,想象一下:

parseExpresion = word "def" <|> word "class" <|> word "import" <|> word "type" <|> word "from"

如果用户键入“frmo”,解析器将转到最后一个分支并引发错误,如 expected "from" but "frmo" was found 然而,如果必须检查所有备选方案,则错误将更像 expected one of "def", "class", "import", "type" of "from" 与实际错字不太接近。

正如我所说,这是一个库设计决定,我只是想说服您有充分的理由不自动尝试所有替代方案,如果您明确想要这样做,请使用 try