当我读Haskell的书时,我偶然发现了三联
我正试着把头转过来,但还是不能理解<|>
我有以下几个问题.
简单的单词(<|>) =一元选择?
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"在完美世界中,如果a is parseFraction将要失败,那么<|>应该使用十进制,但事实并非如此。但是当我使用“尝试”的时候,它是有效的。
parseRationalOrDecimal = try (Left <$> parseFraction) <|> (Right<$> decimal)
发布于 2021-09-08 09:52:59
原因是由于parseFraction在失败之前消耗了输入,因此被认为是选择中的正确分支。让我给你举个例子:
假设您正在编写python解析器,您必须决定声明是class还是函数(关键字def),然后编写
parseExpresion = word "def" <|> word "class" -- DISCLAIMER: using a ficticious library然后,如果用户写入def或class,它将匹配,但如果用户写入det,它将尝试第一个分支并匹配de,然后由于找到t而无法匹配预期的f。它不会费心尝试下一个解析器,因为错误被认为是在第一个分支中。尝试class解析器没有什么意义,因为错误可能在第一个分支中。
在您的示例中,parseFraction匹配一些数字,然后由于找不到/而失败,然后它就不用去尝试decimal解析器了。
这是一个设计决策,其他一些库使用不同的约定(例如:Attoparsec总是在失败时回溯),有些函数声称“不使用输入”(例如:notFollowedBy)
请注意,这里有一个折衷方案:
First:如果<|>的行为与您预期的一样,则如下所示
parse parseRationalOrDecimal "123456789A"首先解析所有数字直到找到"A“,然后再解析!所有数字直到找到"A”为止.所以,做同样的计算两次,只是为了返回一个失败。
第二个:如果您更关心错误消息,那么当前的行为就更方便了。按照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。
https://stackoverflow.com/questions/69094408
复制相似问题