了解三连胜解析器 <|> 并尝试
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 但这不是案子。
但是当我使用时,它会起作用。
- 我错过了什么?
- 为什么我们需要使用 try 而 <|> 应该在第一次失败时 运行 第二个解析器?
parseRationalOrDecimal = try (Left <$> parseFraction) <|> (Right<$> decimal)
原因是因为parseFraction
在失败前消耗了输入,因此,它被认为是选择中的正确分支。让我举个例子:
假设你正在编写一个 python 解析器,你必须决定声明是 class
还是函数(关键字 def
),然后你写
parseExpresion = word "def" <|> word "class" -- DISCLAIMER: using a ficticious library
然后如果用户写def
或class
它会匹配,但是如果用户写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
。
在阅读 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 但这不是案子。
但是当我使用时,它会起作用。
- 我错过了什么?
- 为什么我们需要使用 try 而 <|> 应该在第一次失败时 运行 第二个解析器?
parseRationalOrDecimal = try (Left <$> parseFraction) <|> (Right<$> decimal)
原因是因为parseFraction
在失败前消耗了输入,因此,它被认为是选择中的正确分支。让我举个例子:
假设你正在编写一个 python 解析器,你必须决定声明是 class
还是函数(关键字 def
),然后你写
parseExpresion = word "def" <|> word "class" -- DISCLAIMER: using a ficticious library
然后如果用户写def
或class
它会匹配,但是如果用户写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
。