简体   繁体   English

Parsec:Applicatives vs Monads

[英]Parsec: Applicatives vs Monads

I'm just starting with Parsec (having little experience in Haskell), and I'm a little confused about using monads or applicatives. 我刚刚开始使用Parsec(在Haskell方面经验不足),而且我对使用monads或applicatives感到有些困惑。 The overall feel I had after reading "Real World Haskell", "Write You a Haskell" and a question here is that applicatives are preferred, but really I have no idea. 阅读“真实世界Haskell”,“写你一个Haskell”之后的整体感觉以及这里的一个问题是应用程序是首选,但我真的不知道。

So my questions are: 所以我的问题是:

  • What approach is preferred? 什么方法是首选的?
  • Can monads and applicatives be mixed (use them when they are more useful than the other) monad和applicatives可以混合使用(当它们比另一个更有用时使用它们)
  • If the last answer is yes, should I do it? 如果最后的答案是肯定的,我应该这样做吗?

It might be worth paying attention to the key semantic difference between Applicative and Monad , in order to determine when each is appropriate. 可能值得关注ApplicativeMonad之间的关键语义差异,以确定何时适合。 Compare types: 比较类型:

(<*>) :: m (s -> t) -> m s -> m t
(>>=) :: m s -> (s -> m t) -> m t

To deploy <*> , you choose two computations, one of a function, the other of an argument, then their values are combined by application. 要部署<*> ,您可以选择两个计算,一个是函数,另一个是参数,然后它们的值由应用程序组合。 To deploy >>= , you choose one computation, and you explain how you will make use of its resulting values to choose the next computation. 要部署>>= ,您可以选择一个计算,并解释如何使用其结果值来选择下一个计算。 It is the difference between "batch mode" and "interactive" operation. 它是“批处理模式”和“交互式”操作之间的区别。

When it comes to parsing, Applicative (extended with failure and choice to give Alternative ) captures the context-free aspects of your grammar. 在解析时, Applicative (扩展为失败和选择提供Alternative )捕获语法的无上下文方面。 You will need the extra power that Monad gives you only if you need to inspect the parse tree from part of your input in order to decide what grammar you should use for another part of your input. 只有当您需要从输入的一部分检查解析树以确定您应该用于输入的另一部分的语法时,您将需要Monad为您提供的额外功率。 Eg, you might read a format descriptor, then an input in that format. 例如,您可能会读取格式描述符,然后读取该格式的输入。 Minimizing your usage of the extra power of monads tells you which value-dependencies are essential. 最大限度地减少对monad额外功能的使用,可以告诉您哪些值依赖是必不可少的。

Shifting from parsing to parallelism, this idea of using >>= only for essential value-dependency buys you clarity about opportunities to spread load. 从解析转向并行,这种使用>>=仅用于基本价值依赖的想法可以让您清楚地了解扩散负载的机会。 When two computations are combined with <*> , neither need wait for the other. 当两个计算与<*>组合时,两者都不需要等待另一个。 Applicative-when-you-can-but-monadic-when-you-must is the formula for speed. 适用时 - 当你必须但是 - monadic-当你必须是速度的公式。 The point of ApplicativeDo is to automate the dependency analysis of code which has been written in monadic style and thus accidentally oversequentialised. ApplicativeDo的要点是自动化对代码的依赖性分析,这种代码以monadic风格编写,因而意外地被反序化。

Your question also relates to coding style, about which opinions are free to differ. 您的问题还涉及编码风格,哪些意见可以自由区分。 But let me tell you a story. 但是,让我告诉你一个故事。 I came to Haskell from Standard ML, where I was used to writing programs in direct style even if they did naughty things like throw exceptions or mutate references. 我从标准ML来到Haskell,在那里我习惯于以直接的方式编写程序,即使他们做了顽皮的事情,如抛出异常或变异引用。 What was I doing in ML? 我在ML做什么? Working on an implementation of an ultra-pure type theory (which may not be named, for legal reasons). 致力于实施超纯类型理论(由于法律原因,可能不会命名)。 When working in that type theory, I couldn't write direct-style programs which used exceptions, but I cooked up the applicative combinators as a way of getting as close to direct style as possible. 在使用该类型理论时,我无法编写使用异常的直接式程序,但我将应用程序组合器作为一种尽可能接近直接样式的方法。

When I moved to Haskell, I was horrified to discover the extent to which people seemed to think that programming in pseudo-imperative do-notation was just punishment for the slightest semantic impurity (apart, of course, from non-termination). 当我搬到Haskell时,我惊恐地发现人们似乎在多大程度上认为伪命令式编程中的编程只是对最轻微的语义杂质的惩罚(当然,除了非终止之外)。 I adopted the applicative combinators as a style choice (and went even closer to direct style with "idiom brackets") long before I had a grasp of the semantic distinction, ie, that they represented a useful weakening of the monad interface. 在我掌握了语义区别之前,我采用了应用组合器作为一种风格选择(并且更接近直接风格与“成语括号”),即它们代表了monad界面的有用弱化。 I just didn't (and still don't) like the way do-notation requires fragmentation of expression structure and the gratuitous naming of things. 我只是没有(并且仍然没有)喜欢这种方式,需要表达结构的碎片化和事物的无偿命名。

That's to say, the same things that make functional code more compact and readable than imperative code also make applicative style more compact and readable than do-notation. 也就是说,与命令式代码相比,使功能代码更紧凑和可读的相同内容也使得应用程序样式比符号更紧凑和可读。 I appreciate that ApplicativeDo is a great way to make more applicative (and in some cases that means faster ) programs that were written in monadic style that you haven't the time to refactor. 我很欣赏ApplicativeDo是一种很好的方式,可以让你有更多的应用程序(在某些情况下,这意味着更快 )以monadic风格编写的程序你没有时间重构。 But otherwise, I'd argue applicative-when-you-can-but-monadic-when-you-must is also the better way to see what's going on. 但除此之外,我认为应用 - 当你可以 - 但是 - monadic-当你必须也是更好的方式来看看发生了什么。

In general, start with whatever makes the most sense to you. 一般来说,从对你最有意义的事情开始。 Afterwards consider the following. 然后考虑以下内容。

It is good practice to use Applicative (or even Functor ) when possible. 在可能的情况下,最好使用Applicative (甚至Functor )。 It is in general easier for a compiler like GHC to optimize these instances since they can be simpler than Monad . 像GHC这样的编译器通常更容易优化这些实例,因为它们可以比Monad更简单。 I think the general community advice post- AMP has been to make as general as possible your constraints. 我认为AMP之后的一般社区建议是尽可能地制定你的约束条件。 I would recommend the use of the GHC extension ApplicativeDo since you can uniformly use do notation while only getting an Applicative constraint when that is all that is needed. 我建议使用GHC扩展名ApplicativeDo因为你可以统一使用do notation,而只需要一个Applicative约束就可以了。

Since the ParsecT parser type is an instance of both Applicative and Monad , you can mix and match the two. 由于ParsecT解析器类型是ApplicativeMonad的实例,因此您可以混合使用它们。 There are situations where doing this is more readable - this all depends on the situation. 在某些情况下,这样做更具可读性 - 这完全取决于具体情况。

Also, consider using megaparsec . 另外,考虑使用megaparsec megaparsec is a more actively maintained just generally cleaner more recent fork of parsec . megaparsec是一个更积极的维护,只是一般更干净的更新近派的parsec

EDIT 编辑

Two things that, rereading my answer and the comments, I really didn't do a good job of clarifying: 有两件事,重读我的回答和评论,我真的没有做好澄清:

  • the main benefit of using Applicative is that for many types it admits much more efficient implementations (eg. (<*>) is more performant than ap ). 使用Applicative的主要好处是,对于许多类型,它允许更有效的实现(例如。 (<*>)ap更高性能)。

  • If you just want to write something like (+) <$> parseNumber <*> parseNumber , there is no need to drop into ApplicativeDo - it would be just more verbose. 如果你只想写像(+) <$> parseNumber <*> parseNumber ,就不需要放入ApplicativeDo - 它会更加冗长。 I'd use ApplicativeDo only when you start finding yourself writing very long or nested applicative expressions. 我只在你开始发现自己编写非常长或嵌套的应用表达式时才使用ApplicativeDo

Following on from @pigworker (I'm too new on here to comment alas) it's worth noting join $ fM <*> ... <*> ... <*> ... as a pattern as well. 继@pigworker(我在这里发表新评论唉)之后,值得注意的是join $ fM <*> ... <*> ... <*> ...作为模式。 It nets you a "bind1, bind2, bind3..." family the same way <$> and <*> get you an "fmap1,fmap2,fmap3" one. 它以“$ bind”和<*>获取“fmap1,fmap2,fmap3”的方式<$>您提供“bind1,bind2,bind3 ...”系列。

As a stylistic thing, when you're used enough to the combinators it's possible to use do much the same as you would use let : as a way to highlight when you wanted to name something. 作为一个文体的事情,当习惯于你够有可能使用组合子do差不多,你会使用let :作为一种彰显,当你想要的东西的名字。 I tend to want to name things more often in parsers for example, because that probably corresponds to names in the spec! 例如,我倾向于在解析器中更频繁地命名,因为这可能与规范中的名称相对应!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM