6

让我们看看代码片段:

pSegmentBegin p i   = pIndentExact i *> ((:) <$> p i <*> ((pEOL *> pSegment p i) <|> pure []))

如果我在解析器中将此代码更改为:

pSegmentBegin p i   = do
    pIndentExact i
    ((:) <$> p i <*> ((pEOL *> pSegment p i) <|> pure []))

我有一个错误:

canot compute minmal length of a parser due to occurrence of a moadic bind, use addLength to override

我认为上述解析器的行为方式应该相同。为什么会出现这个错误?

编辑

上面的例子非常简单(为了简化问题),如下所述,这里没有必要使用 do 表示法,但我希望它使用的真实情况如下:

pSegmentBegin p i   = do
    j <- pIndentAtLast i
    (:) <$> p j <*> ((pEOL *> pSegments p j) <|> pure [])

我注意到在 do 语句之前添加“addLength 1”可以解决问题,但我不确定它是否是正确的解决方案:

pSegmentBegin p i   = addLength 2 $ do
    j <- pIndentAtLast i
    (:) <$> p j <*> ((pEOL *> pSegments p j) <|> pure [])
4

3 回答 3

8

正如我多次提到的,应尽可能避免使用单子接口。让我试着解释一下为什么首选应用接口。

我的库的显着特点之一是它通过插入或删除问题来执行错误纠正。当然,我们可以在这里无限地向前看,但这会使该过程非常昂贵。所以我们只对三个步骤进行有限的前瞻。

现在假设我们必须插入一个表达式,其中一个表达式替代项是:

expr := "if" expr "then" expr "else" expr

然后我们想排除这个替代方案,因为选择这个替代方案需要插入另一个表达式等。所以我们对替代方案进行抽象解释,并确保在平局的情况下(即有限前瞻的相同成本)我们采取一个的非递归替代方案。

不幸的是,这种方案在编写单子解析器时会失效,因为绑定右侧的长度可能取决于左侧的结果。因此,我们发出错误消息,并请求程序员提供一些帮助,以指示此替代方案可能消耗的令牌数量。实际值并不重要,只要您不为递归且可能导致无限插入的东西提供有限长度。它仅用于在插入的情况下选择最短的替代方案。

这种抽象解释有一些成本,如果您以单子风格编写所有解析器,则不可避免地会一遍又一遍地重复此分析。所以:如果有应用程序替代方案,请不要在使用此库时编写 MONADIC 样式解析器。

于 2013-08-17T19:00:02.497 回答
5

它试图静态分析需要读取多少输入以优化性能,但这种优化需要静态已知的解析器结构——可以由Applicatives 构建的那种,因为解析器效果不能依赖于解析器值等等(>>=)做。

所以这就是问题所在 - 当您使用do符号时,它会转换为破坏预测器的 Monadic 绑定Applicative。如果库只公开两个接口之一,这样就不会发生这种错误,那就太好了,但是如果您在同一个解析器中同时使用这两个接口,则会出现一些不一致。

由于这种使用do是完全不必要的——你没有使用 monadic 接口给你的额外功能——最好避免它。

于 2013-08-16T16:50:51.987 回答
1

我有一个解决方法,我在 uuparsinglib 中使用单子解析器。这是一个自我回答: Monadic parse with uu-parsinglib

你可能会发现它很有用

于 2013-08-17T20:36:05.153 回答