在您的情况下,基础单子是Identity
. 然而 ParsecT 与大多数 monad 转换器的不同之处在于它是Monad
类的一个实例,即使类型参数m
不是。如果您查看源代码,您会注意到(Monad m) =>
实例声明中缺少“”。
那么你问自己,“如果我有一个非平凡的 monad 堆栈,它将在哪里使用?”
这个问题有三个答案:
它用于uncons
流中的下一个标记:
class (Monad m) => Stream s m t | s -> t where
uncons :: s -> m (Maybe (t,s))
请注意,它uncons
接受一个s
(令牌流t
)并返回其包装在您的 monad 中的结果。这允许人们在获得下一个令牌的过程中甚至在获得下一个令牌的过程中做有趣的事情。
它用于每个解析器的结果输出。这意味着您可以创建不接触输入但在底层 monad 中执行操作的解析器,并使用组合器将它们绑定到常规解析器。换句话说,lift (x :: m a) :: ParsecT s u m a
.
m
最后,RunParsecT 和朋友的最终结果(直到你建立到被 替换的地步Identity
)返回包装在这个 monad 中的结果。
这个单子与Haskell 中 Monadic Parsing 中的单子没有关系。在这种情况下,Hutton 和 Meijer 指的是 ParsecT 本身的 monad 实例。在 Parsec-3.0.0 及更高版本中 ParsecT 已成为具有底层 monad 的 monad 转换器这一事实与本文无关。
但是,我认为您正在寻找的是可能结果列表的去向。在 Hutton 和 Meijer 中,解析器返回所有可能结果的列表,而 Parsec 固执地只返回一个。我认为您正在查看m
结果,并认为结果列表必须隐藏在某个地方。它不是。
Parsec 出于效率的原因,选择更喜欢 Hutton 和 Meijer 的结果列表中的第一个匹配结果。这让它在 Hutton 和 Meijer 列表的尾部以及令牌流的前面丢弃未使用的结果,因为我们从不回溯。在 parsec 中,给定组合 parser a <|> b
,如果使用a
任何输入,b
则永远不会被评估。解决这个问题的方法是try
将状态重置回如果a
失败然后评估的位置b
。
您在评论中询问这是否是使用Maybe
or完成的Either
。答案是“几乎但不完全”。如果您查看低级别run*
函数,您会看到它们返回一个代数类型,它告诉天气输入已被消耗,然后是第二个给出结果或错误消息。这些类型的工作方式有点像Either
,但即使它们也不直接使用。与其进一步扩展这一点,我将向您推荐 Antoine Latter的帖子,该帖子解释了它是如何工作的以及为什么这样做。