我刚刚开始使用Parsec(在Haskell方面经验不足),而且我对使用monads或applicatives感到有些困惑。阅读后的整体感觉"真实世界Haskell","写你一个Haskell"这里的问题是申请人是首选,但我真的不知道。
所以我的问题是:
答案 0 :(得分:64)
值得注意Applicative
和Monad
之间的关键语义差异,以确定每个时间是否合适。比较类型:
(<*>) :: m (s -> t) -> m s -> m t
(>>=) :: m s -> (s -> m t) -> m t
要部署<*>
,您可以选择两个计算,一个是函数,另一个是参数,然后它们的值由应用程序组合。要部署>>=
,您可以选择一个计算,并解释如何使用其结果值来选择下一个计算。这是&#34;批处理模式和#34;之间的区别。和#34;互动&#34;操作
在解析时,Applicative
(扩展失败并选择赋予Alternative
)捕获语法的无上下文方面。只有当您需要从输入的一部分检查解析树时,才需要Monad
给你的额外功率,以便决定你应该为输入的另一部分使用什么语法。例如,您可以读取格式描述符,然后读取该格式的输入。最大限度地减少对monad额外功能的使用,可以告诉您哪些值依赖是必不可少的。
从解析转向并行,这种仅将>>=
用于基本价值依赖的想法可以让您清楚地了解扩散负载的机会。当两个计算与<*>
组合时,两者都不需要等待另一个。适用时 - 当你必须但是 - monadic-当你必须是速度的公式。 ApplicativeDo
的要点是自动化对代码的依赖性分析,这种代码以monadic风格编写,因而意外地被反序化。
您的问题还涉及编码风格,哪些意见可以自由区分。但是,让我告诉你一个故事。我从标准ML来到Haskell,在那里我习惯于以直接的方式编写程序,即使他们做了顽皮的事情,如抛出异常或变异引用。我在ML做什么?致力于实施超纯类型理论(由于法律原因,可能不会命名)。在 in 中使用类型理论时,我无法编写使用异常的直接式程序,但我将应用程序组合器作为一种尽可能接近直接样式的方法。< / p>
当我搬到Haskell时,我惊恐地发现人们似乎在多大程度上认为伪命令式编程中的编程只是对最轻微的语义杂质的惩罚(当然,除了非终止之外) 。在我掌握语义区别之前,我采用了应用组合器作为一种风格选择(并且更接近直接风格与#34;成语括号&#34;),即它们代表了monad的有用弱化接口。我只是没有(并且仍然没有)喜欢这种方式,需要表达式结构的碎片化和事物的无偿命名。
这就是说,与命令式代码相比,使功能代码更紧凑和可读的相同内容也使得应用程序样式比符号更紧凑和可读。我很欣赏ApplicativeDo
是一种很好的方式来制作更多适用于(在某些情况下意味着更快)的程序,这些程序是用monadic风格编写的,你没有时间重构。但除此之外,我认为应用 - 当你可以 - 但是 - 一元 - 当你必须也是更好的方式来看看发生了什么。
答案 1 :(得分:12)
一般来说,从对你最有意义的事情开始。然后考虑以下内容。
最好尽可能使用Applicative
(甚至Functor
)。像GHC这样的编译器通常更容易优化这些实例,因为它们可以比Monad
更简单。我认为一般社区建议帖子AMP一直是尽可能地制定你的约束。我建议使用GHC扩展程序ApplicativeDo
,因为您可以统一使用do
表示法,而只需获得Applicative
约束时就可以了。
由于ParsecT
解析器类型是Applicative
和Monad
的实例,因此您可以将两者混合使用。在某些情况下,这样做更具可读性 - 这完全取决于具体情况。
另外,请考虑使用megaparsec
。 megaparsec
parsec
是一个更积极维护的,通常更清晰,更近期的Applicative
分叉。
有两件事,重读我的回答和评论,我真的没有做好澄清的事情:
使用(<*>)
的主要好处是,对于许多类型,它承认更有效的实现(例如。(+) <$> parseNumber <*> parseNumber
比ap
更高效。)
如果您只想写ApplicativeDo
之类的内容,则无需进入ApplicativeDo
- 它会更加冗长。只有当你开始发现自己编写很长或嵌套的应用表达式时,我才会使用 i = 1 # this can be anything depending on what to user chooses
x = 5
y = 10
if i >= 1:
z = x + y
if i >=2:
# Here I want to return what would have happened if i == 1, in addition other stuff:
r = x^3 - y
if i >= 3:
# Again, execute all the stuff that would have happened if i == 1 or == 2, an addition to something new:
# execute code for i == 1 and i == 2 as well as:
s = i^3 + y^2
。
答案 2 :(得分:6)
继续@pigworker(我在这里发表新评论唉),值得注意join $ fM <*> ... <*> ... <*> ...
作为模式。它以<$>
和<*>
获取“fmap1,fmap2,fmap3”的方式为您提供“bind1,bind2,bind3 ...”系列。
作为一种风格化的东西,当你对组合器的使用足够时,可以使用do
与使用let
的方式相同:作为一种突出显示何时想要命名的方法。例如,我倾向于在解析器中更频繁地命名,因为这可能与规范中的名称相对应!