无法理解解析器组合器中的“pMany”和“pMany1”

问题描述

我正在尝试理解解析器组合器中的 pMany 和 pMany1 函数

newtype Parser s t = P([s] -> [(t,[s])])
pMany,pMany1 :: Parser s a → Parser s [a] 
pMany p =(:) <$> p <*> pMany p `opt` [] 
pMany1 p = (:) <$> p <*> pMany p

据我所知,pMany 会尽可能多地运行解析器,并在 [a] 中收集最终结果。我不明白的是它如何跟踪每次运行之间的结果。应用组合是上下文无关的,不应该记住两者之间的状态。对吗?

非常感谢!

解决方法

让我们分解一下;

pMany p = (:) <$> p <*> pMany p `opt` [] 

基本上是指

pMany p = (fmap (:) p <*> pMany p) `opt` [] 

这个表达式由两部分组成:

  • 解析fmap (:) p <*> pMany p
  • 如果以上失败,结果是空列表

这里的想法是尝试解析“一个元素并尝试解析更多”或“不解析任何内容”,如果上一步没有成功。我认为第二部分是可以理解的,让我们专注于第一部分。

这里我们需要了解 fmap<*> 是如何工作的:

  • fmap 非常简单:它接受一个函数 a -> b、一个解析器 Parser s a 并返回 Parser s b。这允许我们显式操作解析器的结果,而无需实际运行它。
  • <*> 的工作方式与 fmap 完全相同,但有一点不同,即函数本身是解析的结果。在(主观上)最理智的实现中:
    • 运行返回函数的左侧解析器(消耗输入)
    • 运行返回参数的右侧解析器(在剩余的输入上)
    • 将上述内容组合成一个解析器,该解析器返回应用于上述参数的函数

那么在这个神秘的 fmap (:) p <*> pMany p 中发生了什么:

  • 首先,我们使用解析器 a 解析某个 p 类型的对象。
  • 然后,在解析上下文中,我们对其应用函数 (:) :: a -> [a] -> [a]。因此,如果我们解析了一个 int 2137,我们现在有 (:) 2137,它与 \rest -> 2137:rest 相同。此时我们有 Parser s ([a] -> [a]) 类型的解析器。
  • 下一步是解析 <*> 运算符的右侧,它是对 pMany 的递归调用。我们可以将其理解为“继续使用相同的算法”。实际上,我们解析了其余的元素。这产生(根据 pMany 的类型)Parser s [a]
  • 最后,我们将前面的结果应用到最后一个,得到一个解析器,该解析器将左元素(用 p 解析)附加到后面的元素(用 pMany p 解析)。从 <*> 的类型我们可以推断出结果类型将是 Parser s [a] 预期的。

这段代码在语义上等同于

pMany p = (do
    someElem <- p
    restElems <- pMany p
    return (someElem : restElems)
  ) `opt` []

pMany1 执行相同的技巧,但如果无法解析第一个元素则失败。请注意,它在没有此属性的之后调用 pMany。因此,我们强制它至少解析一件事(“解析一个然后解析任意数字”)。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...