考虑以下代码:
import Text.ParserCombinators.ReadP
prefixExpr :: ReadP Int
prefixExpr = choice [readPlus, readS_to_P reads]
where
readPlus = do
string "+ "
s1 <- prefixExpr
string " "
s2 <- prefixExpr
return $ s1 + s2
p $. s = case [ x | (x,"") <- p `readP_to_S` s ] of
[x] -> Right x
[ ] -> Left "mkRead: no parse"
_ -> Left "mkRead: ambiguous parse"
效果很好!
λ prefixExpr $. "+ 1 + 2 3"
Right 6
但是此变体不能:
infixExpr :: ReadP Int
infixExpr = choice [readPlus, readS_to_P reads]
where
readPlus = do
s1 <- infixExpr
string " + "
s2 <- infixExpr
return $ s1 + s2
实际上,它挂了。
λ infixExpr $. "1 + 2 + 3"
^CInterrupted.
λ infixExpr $. "1"
^CInterrupted.
我了解到infixExpr
进入无限递归试图确定“ 1”是否为数字
或表达式的开头,但我想知道如何避免这种情况。我真的不需要我
程序可以无限期地探索所有可能性,只是选择最明显的可能性而无视
其他人一共。我该怎么办?
我尝试的一件事是对递归深度进行限制。
infixExpr :: Int -> ReadP Int
infixExpr 0 = number
infixExpr n = choice [plus, number]
where
plus = do
s1 <- infixExpr (n - 1)
string " + "
s2 <- infixExpr (n - 1)
return $ s1 + s2
这是可行的,但是由于它的指数复杂性,实际上是没有用的。
答案 0 :(得分:3)
如@chi所建议,通过分解来消除左递归:
infixExpr :: ReadP Int
infixExpr = number >>= \x -> choice [plus x, eof >> return x]
where
plus x = do
string " + "
y <- infixExpr
return $ x + y
有效:
λ infixExpr $. "1"
Right 1
λ infixExpr $. "1 + 2"
Right 3
λ infixExpr $. "1 + 2 + 3"
但是我对此并不满意。我希望原来的语法能起作用,因为它看起来更像是人类的思维。
说句公道话,只需很小的编辑,就可以更轻松地实现相同的效果:
infixExpr :: ReadP Int
infixExpr = choice [plus, number]
where
plus = do
s1 <- number -- Notice!
string " + "
s2 <- infixExpr
return $ s1 + s2
我前一段时间已经想通了,但是我真的想对plus
的定义保持对称。