你如何使用Control.Applicative来编写更干净的Haskell?

时间:2010-01-20 19:52:29

标签: haskell coding-style

在最近的answer to a style question中,我写了

main = untilM (isCorrect 42) (read `liftM` getLine)

isCorrect num guess =
  case compare num guess of
    EQ -> putStrLn "You Win!" >> return True
    ...

Martijn有用地提出了替代方案:

main = untilM (isCorrect 42) (read <$> getLine)

EQ -> True <$ putStrLn "You Win!"

使用Control.Applicative的抽象可以使Haskell代码中的哪些常见模式更清晰?有效使用Control.Applicative时要记住哪些有用的经验法则?

3 个答案:

答案 0 :(得分:48)

在回答你的问题时有很多话要说,但是,既然你问过,我会提供这个“经验法则”。

如果您正在使用do - 表示法,并且您正在排序的表达式[2]中未使用生成的值[1],那么该代码可以转换为Applicative样式。同样,如果您在已排序的表达式中使用一个或多个生成的值,则必须使用Monad并且Applicative不足以实现相同的代码。

例如,让我们看看以下代码:

do a <- e1
   b <- e2
   c <- e3
   return (f a b c)

我们发现,在<-右侧的任何表达式中,都不会显示任何生成的值(abc)。因此,我们可以将其转换为使用Applicative代码。这是一个可能的转变:

f <$> e1 <*> e2 <*> e3

和另一个:

liftA3 f e1 e2 e3

另一方面,以这段代码为例:

do a <- e1
   b <- e2 a
   c <- e3
   return (f b c)

此代码无法使用Applicative [3],因为稍后在理解中的表达式中使用生成的值a。这必须使用Monad来获得结果 - 尝试将其计入Applicative以了解原因。

关于这个主题还有一些有趣且有用的细节,但是,我只是想给你这个经验法则,你可以略过do - 理解并快速确定它是否可以被考虑到Applicative样式代码。

[1]显示在<-左侧的那些。

[2]显示在<-右侧的表达式。

[3]严格来说,部分内容可以通过考虑e2 a

答案 1 :(得分:44)

基本上,monad也是应用函子[1]。因此,只要您发现自己使用liftMliftM2等,就可以使用<*>将计算链接在一起。从某种意义上说,你可以认为applicative functor与函数类似。通过执行f可以解除纯函数f <$> x <*> y <*> z

与monad相比,applicative functor无法有选择地运行其参数。所有论点都会产生副作用。

import Control.Applicative

ifte condition trueClause falseClause = do
  c <- condition
  if c then trueClause else falseClause

x = ifte (return True) (putStrLn "True") (putStrLn "False")

ifte' condition trueClause falseClause = 
  if condition then trueClause else falseClause

y = ifte' <$> (pure True) <*> (putStrLn "True") <*> (putStrLn "False")

x仅输出True,而y依次输出TrueFalse

[1] The Typeclassopedia。强烈推荐。

[2] http://www.soi.city.ac.uk/~ross/papers/Applicative.html。虽然这是一篇学术论文,但并不难理解。

[3] http://learnyouahaskell.com/functors-applicative-functors-and-monoids#applicative-functors。很好地解释了这笔交易。

[4] http://book.realworldhaskell.org/read/using-parsec.html#id652399。显示monadic Parsec库如何以应用方式使用。

答案 2 :(得分:10)