You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Haskell Parsec组合子顺序为何影响加法表达式解析结果?

加法表达式解析器的Parsec组合子顺序问题

我想要实现一个解析加法表达式的简单解析器,写出了如下代码:

import Text.Parsec.Char
import Text.Parsec.String
import Text.ParserCombinators.Parsec

data Expr = Number Float |
            Add Expr Expr

number :: Parser Expr
number = do
    n <- try $ many1 digit
    return $ Number $ read n

add :: Parser Expr
add = do
    e1 <- number
    char '+'
    e2 <- number
    return $ Add e1 e2

expr :: Parser Expr
expr =  try number <|> try add
       

p :: String -> Either ParseError Expr
p = parse (do{e <- expr; eof; return e}) "error"

执行结果如下:

ghci> parse add "err" "1+2"
Right (Add (Number 1.0) (Number 2.0))
ghci> p "1"
Right (Number 1.0)
ghci> p "1+2"
Left "error" (line 1, column 2):
unexpected '+'
expecting digit or end of input

但当我修改expr中组合子的顺序为:

expr :: Parser Expr
expr =  try add <|> try number

执行结果变为:

ghci> p "1+2"
Right (Add (Number 1.0) (Number 2.0))

为什么会出现这种情况?我原以为try关键字会强制每个<|>后的解析器重新开始解析。我计划扩展这个解析器,因此希望现在就能理解其中的原因。我的实际程序规模已更大,但该问题仍独立存在。


原因解析

Parsec的(<|>)组合子遵循优先尝试左侧解析器,仅当左侧解析失败且未消耗任何输入时,才会尝试右侧解析器的核心规则,这是问题的关键:

  1. 当expr为try number <|> try add时:

    • 解析"1+2"时,左侧的number会先匹配字符串开头的"1",解析成功并消耗了这部分输入,返回Number 1.0。
    • 随后p中的eof检查会发现还剩"+2"未解析,导致整个流程失败。
    • 这里的try number并未触发回退,因为number本身解析成功了——try仅在解析器自身失败时才会回退已消耗的输入,后续的eof失败并不会让<|>回退去尝试右侧的add。
  2. 当expr为try add <|> try number时:

    • 解析"1+2"时,左侧的add会完整匹配整个加法表达式:先调用number解析"1",接着匹配"+",再调用number解析"2",全程消耗了所有输入,返回Add (Number 1.0) (Number 2.0)。
    • 后续的eof检查顺利通过,整个解析流程成功。

你对try的理解存在偏差:try p的作用是当解析器p本身执行失败时,回退它已经消耗的输入,让后续解析器能从原始位置重新尝试。但如果p解析成功,无论后续步骤是否失败,try都不会触发回退操作。

内容的提问来源于stack exchange,提问作者Tristan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 08:20:31