Parsec:Applicatives vs Monads

Jos*_*uel 21 haskell parsec

我刚刚开始使用Parsec(在Haskell方面经验不足),而且我对使用monads或applicatives感到有些困惑.阅读"真实世界Haskell","写你一个Haskell"之后的整体感觉以及这里的一个问题是应用程序是首选,但我真的不知道.

所以我的问题是:

  • 什么方法是首选的?
  • monad和applicatives可以混合使用(当它们比另一个更有用时使用它们)
  • 如果最后的答案是肯定的,我应该这样做吗?

pig*_*ker 65

可能值得关注Applicative和之间的关键语义差异Monad,以确定何时适当.比较类型:

(<*>) :: m (s -> t) -> m s -> m t
(>>=) :: m s -> (s -> m t) -> m t
Run Code Online (Sandbox Code Playgroud)

要进行部署<*>,您可以选择两个计算,一个是函数,另一个是参数,然后它们的值由应用程序组合.要进行部署>>=,您可以选择一个计算,并解释如何使用其结果值来选择下一个计算.它是"批处理模式"和"交互式"操作之间的区别.

解析时,Applicative(扩展失败和选择Alternative)会捕获语法的无上下文方面.Monad只有当您需要从输入的一部分检查解析树时才需要额外的功率,以便决定您应该为输入的另一部分使用哪种语法.例如,您可能会读取格式描述符,然后读取该格式的输入.最大限度地减少对monad额外功能的使用,可以告诉您哪些值依赖是必不可少的.

从解析转向并行化,这种>>=仅用于基本价值依赖的想法可以让您清楚地了解扩散负载的机会.当两个计算结合使用时<*>,既不需要等待另一个计算.适用时 - 当你必须但是 - monadic-当你必须是速度的公式.重点ApplicativeDo是自动化代码的依赖性分析,这种代码以monadic风格编写,因而意外地被反序化.

您的问题还涉及编码风格,哪些意见可以自由区分.但是,让我告诉你一个故事.我从标准ML来到Haskell,在那里我习惯于以直接的方式编写程序,即使他们做了顽皮的事情,如抛出异常或变异引用.我在ML做什么?致力于实施超纯类型理论(由于法律原因,可能不会命名).工作时那种理论,我不能写一个使用过异常直接式的节目,但我做了应用性组合子的越来越接近直接的风格尽可能的一种方式.

当我搬到Haskell时,我惊恐地发现人们似乎在多大程度上认为伪命令式编程中的编程只是对最轻微的语义杂质的惩罚(当然,除了非终止之外).在我掌握了语义区别之前,我采用了应用组合器作为一种风格选择(并且更接近直接风格与"成语括号"),即它们代表了monad界面的有用弱化.我只是没有(并且仍然没有)喜欢这种方式,需要表达结构的碎片化和事物的无偿命名.

也就是说,与命令式代码相比,使功能代码更紧凑和可读的相同内容也使得应用程序样式比符号更紧凑和可读.我很欣赏这ApplicativeDo是一个很好的方法,可以使用更多的应用程序(在某些情况下,这意味着更快)以monadic风格编写的程序,你没有时间重构.但除此之外,我认为应用 - 当你可以 - 但是 - monadic-当你必须也是更好的方式来看看发生了什么.

  • @RobStewart Simon M的工作就是我在写这个答案时的想法.同时,无限流形成一个monad,其中`repeat`是`return`,`join`取无限方阵的对角线.当然,如果你只想要对角线,那么甚至计算thunk矩阵也是愚蠢的:`>> =`至少产生一个三角形.在这种意义上组合流的合理方法是*压缩*,这正是应用行为.它可能不完全是并行性,但它是另一个例子,其中`>> =`的`<*>`的默认实现是无效的过度. (2认同)

Ale*_*lec 12

一般来说,从对你最有意义的事情开始.然后考虑以下内容.

在可能的情况下使用Applicative(或甚至Functor)是一种好的做法.像GHC这样的编译器通常更容易优化这些实例,因为它们可以更简单Monad.我认为AMP之后的一般社区建议是尽可能地制定你的约束条件.我建议使用GHC扩展,ApplicativeDo因为你可以统一使用do符号,而只有在需要的Applicative时候才能获得约束.

由于ParsecT解析器类型既是实例ApplicativeMonad,你可以混合和匹配两个.在某些情况下,这样做更具可读性 - 这完全取决于具体情况.

另外,考虑使用megaparsec.megaparsec是一个更积极维护,只是一般更干净,更近期的分叉parsec.

编辑

有两件事,重读我的回答和评论,我真的没有做好澄清:

  • 使用的主要好处Applicative是,对于许多类型,它允许更高效的实现(例如,(<*>)性能更高ap).

  • 如果你只是想写一些类似的东西(+) <$> parseNumber <*> parseNumber,就没有必要进入ApplicativeDo- 它会更加冗长.我ApplicativeDo只会在你开始发现自己编写非常长或嵌套的应用表达式时使用.


小智 6

继@pigworker之后(我在这里发表评论唉),值得注意的join $ fM <*> ... <*> ... <*> ...是一个模式.它以相同的方式为您提供"bind1,bind2,bind3 ......"系列,<$>并为<*>您提供"fmap1,fmap2,fmap3"系列.

作为一种风格化的东西,当你对组合器的使用足够时,你可以使用do与你使用的相同的东西let:作为一种突出你想要命名的方法.例如,我倾向于在解析器中更频繁地命名,因为这可能与规范中的名称相对应!

  • 你能扩大这个澄清吗?第一段相当含糊;第二个可以使用一些例子。 (2认同)