我一直在编写一个 attoparsec 解析器,并且一直在遇到一种模式,我想将解析器转换为递归解析器(将它们与 monad bind >>= 运算符递归组合)。
所以我创建了一个函数来将解析器转换为递归解析器,如下所示:
recursiveParser :: (a -> A.Parser a) -> a -> A.Parser a
recursiveParser parser a = (parser a >>= recursiveParser parser) <|> return a
如果您有递归数据类型,这很有用
data Expression = ConsExpr Expression Expression | EmptyExpr
parseRHS :: Expression -> Parser Expression
parseRHS e = ConsExpr e <$> parseFoo
parseExpression :: Parser Expression
parseExpression = parseLHS >>= recursiveParser parseRHS
where parseLHS = parseRHS EmptyExpr
有没有更惯用的解决方案?似乎recursiveParser
应该是某种折叠......我也在sepBy
文档中看到,但这种方法似乎更适合我的应用程序。
编辑:哦,实际上现在我想它实际上应该类似于fix
......不知道我是怎么忘记的。
EDIT2: Rotsor 在我的例子中用他的替代方案提出了一个很好的观点,但我担心我的 AST 实际上比这更复杂一些。它实际上看起来更像这样(尽管这仍然是简化的)
data Segment = Choice1 Expression
| Choice2 Expression
data Expression = ConsExpr Segment Expression
| Token String
| EmptyExpr
其中字符串a -> b
括号在右侧,c:d
括号在左侧,:
绑定比->
.
即a -> b
评估为
(ConsExpr (Choice1 (Token "a")) (Token "b"))
并c:d
评估为
(ConsExpr (Choice2 (Token "d")) (Token "c"))
我想我可以使用foldl
一个和foldr
另一个,但那里仍然有更多的复杂性。请注意,它以一种有点奇怪的方式递归,因此"a:b:c -> e:f -> :g:h ->"
实际上是一个有效的字符串,但"-> a"
不是"b:"
。最后fix
对我来说似乎更简单。我已经像这样重命名了递归方法:
fixParser :: (a -> A.Parser a) -> a -> A.Parser a
fixParser parser a = (parser a >>= fixParser parser) <|> pure a
谢谢。