2011-10-22 63 views

回答

82

monadic和applicative分析之间的主要区别在于如何处理顺序组合。在应用解析器的情况下,我们使用(<*>),而对于monad,我们使用(>>=)

(<*>) :: Parser (a -> b) -> Parser a -> Parser b 
(>>=) :: Parser a -> (a -> Parser b) -> Parser b 

的单子方法更为灵活,因为它允许第二部分的语法依赖于结果从第一个,但我们很少需要在实践中,这种额外的灵活性。

你可能会认为,有一些额外的灵活性,不能伤害,但在现实中就可以了。它阻止我们在分析器上执行有用的静态分析而不运行它。例如,假设我们想知道解析器是否可以匹配空字符串,以及匹配中可能的第一个字符是什么。我们想要的功能

empty :: Parser a -> Bool 
first :: Parser a -> Set Char 

通过应用解析器,我们可以轻松回答这个问题。 (我在这里作弊一点。试想一下,我们有相应(<*>)(>>=)在我们的候选人分析器“语言”数据构造)。

empty (f <*> x) = empty f && empty x 
first (f <*> x) | empty f = first f `union` first x 
       | otherwise = first f 

然而,有一元解析器,我们不知道是什么,第二部分的语法是不知道的输入。

empty (x >>= f) = empty x && empty (f ???) 
first (x >>= f) | empty x = first x `union` first (f ???) 
       | otherwise = first x 

通过允许更多,我们能够推理得更少。这类似于动态和静态类型系统之间的选择。

但这个又是什么意义呢?我们可以使用这些额外的静态知识来做什么?那么,我们可以通过比较下一个字符与每个备选方案的first集来避免LL(1)解析中的回溯。我们还可以通过检查两个备选方案的first组是否重叠来静态确定这是否会不明确。

另一个例子是它可以用于错误恢复,如S.Doaitse Swierstra和Luc Duponcheel在论文Deterministic, Error-Correcting Combinator Parsers中所示。

但通常,应用性和一元分析之间的选择已经被你使用的解析库的作者提出。当像Parsec这样的库暴露这两个接口时,选择使用哪一个纯粹是一种风格。在某些情况下,应用程序代码比单向代码更易于阅读,有时反过来也是如此。

+5

等一下!我一直认为直到今天,当我想到'空'测试也可以应用于monadic解析器。原因是我们可以通过在空字符串上应用分析器x来获得您命名为'???'的值。更一般地说,您可以将空字符串提供给解析器,看看会发生什么。同样,至少在函数形式'first :: Parser a - >(Char - > Bool)'中可以获得第一个字符集合。当然,将后者转换为“设置字符”会涉及字符的低效枚举,这就是应用函子具有优势的地方。 –

+5

@ HeinrichApfelmus你不能以这种方式得到“空”的答案。或者你*可以*,但这就像给出了[让我们运行程序并看看它是否暂停]的[http://en.wikipedia.org/wiki/Halting_problem]的答案。 – phadej

+3

@hammar:如果我们在空x中运行'let x = pure f <*> y <*> x怎么办?如果'empty y'是'False',那么计算不会终止......只是为了指出,并不那么简单。 – phadej

4

随着Parsec的使用Applicative的好处只是风格。 Monad的优势在于它更强大 - 您可以实现上下文相关的解析器。

如果仅适用,Doaitse Swierstra的UU解析更高效。

+0

ISTR是形式上的,因为哈斯克尔允许无限制文法,单子实际上并没有增加识别语言的数量。 – luqui

+3

@luqui我很好奇你的评论。这是一种语言。字母表是Haskell'String's,语言是所有字母相同的单词集合。作为一元解析器,这是非常简单的:'option [](anyToken >> = many。exactToken)'(其中'anyToken'和'exactToken'实际上并不是Parsec库的一部分,但可能应该是这样;问我,如果你不确定他们做什么)。相应的应用解析器将如何查看? –

+2

@stephen,你可以给上下文敏感的解析器一个参考吗?我很好奇monadic和适用性解析器的确切功能是什么。 – sdcvvc

2

Monads严格地比应用程序更有特色的抽象。你可以写

instance (Monad m) => Applicative m where 
    pure = return 
    (<*>) = ap 

但是没有办法写

instance (Applicative a) => Monad a where 
    return = pure 
    (>>=) = ??? 

所以,是的,它本质上是风格的问题。我想如果你使用returnap,那么应该有没有性能损失而不是使用pure<*>由于Applicative是一个比Monad小得多的接口,这意味着<*>有时可以比ap更高度优化。 (但是,通过巧妙的GHC重写规则,无论如何,通常都可以实现相同的优化。)

是否是monadic解析?

由于Monads是应用程序的一个子集,因此我会得出结论,应用程序解析是monadic解析的一个子集。

+0

你的意思是说Monad是应用程序的_superset? – Guildenstern

+1

@Guildenstern Monadic操作是Applicative操作的超集。但用另一种方式说:类型有一个'Monad'的实例是具有'Applicative'实例的类型的一个子集。在谈到“Monad”和“Applicatives”时,通常指的是类型,而不是操作。 –

+0

“我想如果你使用'return'和'ap',那么在使用'pure'和'<*>''时就不会有性能损失。” IIRC认为情况并非如此。有很多情况(解析器只是其中之一),其中'<*>'胜过'ap'。 – semicolon

10

我认为主观原因应用解析器优于一元解析器的主要原因与在任何上下文中优先使用应用代码而不是一次性代码的主要原因相同:功能较弱,应用程序更易于使用。

这是一个更一般的工程格言的实例:使用最简单的工具完成工作。小推车可以使用时不要使用叉车。不要使用台锯切割优惠券。当它可能是纯粹的时候,不要在IO中编写代码。把事情简单化。

但有时候,你需要额外的力量Monad。这一点的确切迹象是,当您需要根据迄今为止计算的内容来更改计算过程时。在解析术语中,这意味着根据迄今为止解析的内容确定如何解析接下来的内容;换句话说,你可以用这种方式构造上下文敏感的语法。

+1

不,“使用最简单的工具”可能看起来像一个很好的经验法则,但事实上并非如此。例如,我们使用计算机写信,但是与一把剪刀相比,一张纸上的计算机就像一张台锯。 –

+0

我的意思是,每一种选择总会有上升和下降的趋势,但仅仅是简单是选择的不好基础。特别是当你决定是否使用Haskell时。 :D –

+2

是的,你说得对。最好是说“正确的工具是效率最高而最简单的工具”。我的描述中缺少的是关于效率的部分:您需要一种足够强大的工具,不仅能够完成工作,还能让工作尽可能简单。但与此同时,你不希望有一种工具不适用于手边的任务,因为这很可能会增加操作的复杂性,使其毫无益处。 –

11

如果解析器是纯粹的应用性,它可以分析它的结构和运行它之前的“优化”了。如果一个解析器是monadic,它基本上是一个图灵完整的程序,对它进行几乎任何有趣的分析都相当于解决暂停问题(即不可能)。

哦,是的,有一个风格相差太大......

+1

Applicative和Monad之间的区别与图灵完备性无关。在Haskell中,优化Monad实例的相对困难仅仅是由于在类型类中单独暴露'(>> =)'的历史错误,使得实例无法提供像'ap'这样的运算符的更优化实现。 Applicative类避免了这个错误,并暴露了'<*>'(相当于'ap')。 –

+0

@PietDelport,我认为麻烦在于,monadic解析器的基本表示通常不适合优化“Applicative”实例,而应用解析器通常不支持'>> ='。 – dfeuer

+3

@PiDelport它完全与图灵完备性有关。 '>> ='接受左侧解析器的结果并将其传递给右侧的解析器,从而使得生成的解析器无法进行静态分析,因为现在解析器本身已经完成。 '<*>'和'<$>'不检查参数,因此当解析器本身的输出完成时,因为您可以将任何东西放在'<$>'的左侧,解析方面本身受到限制并可进行静态分析。 – semicolon