问题描述
我正在尝试理解解析器组合器中的 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]
。因此,如果我们解析了一个 int2137
,我们现在有(:) 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
。因此,我们强制它至少解析一件事(“解析一个然后解析任意数字”)。