你如何使用Control.Applicative来编写更清洁的Haskell?

我写了一个最近对风格问题的回答

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

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

Martijn有用地build议了替代scheme:

 main = untilM (isCorrect 42) (read <$> getLine) EQ -> True <$ putStrLn "You Win!" 

通过使用Control.Applicative抽象,可以使Haskell代码中哪些常见模式变得更清晰? 记住有效使用Control.Applicative的有用的经验法则是什么?

回答你的问题还有很多,但是,既然你问了,我会提供这个“经验法则”。

如果您正在使用do -notation,并且您所生成的值[1]未在您正在sorting的expression式[2]中使用,那么该代码可以转换为Applicative样式。 同样,如果在sorting的expression式中使用一个或多个生成的值,则必须使用MonadApplicative的强度不足以实现相同的代码。

例如,让我们看看下面的代码:

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

我们看到,在<-的右边的expression式中没有任何一个生成的值( abc )出现。 因此,我们可以将其转换为使用Applicative代码。 这是一个可能的转变:

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

另一个:

 liftA3 f e1 e2 e3 

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

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

该代码不能使用Applicative [3],因为生成的值a稍后在理解的expression式中使用。 这必须使用Monad来得到它的结果 – 尝试将其分解到Applicative以得到原因。

关于这个问题还有一些有趣的和有用的细节,但是我只是想给你一个这样的经验法则,你可以浏览一个do comprehension,并且很快确定它是否可以被分解到Applicative样式代码中。

[1]那些出现在<-的左边。

[2]表示出现在<-的右侧。

[3]严格来说,其中的一部分可以通过分解e2 a

基本上,单子也是应用函子[1]。 所以,只要你发现自己使用了liftMliftM2等,就可以使用<*>来链接计算。 从某种意义上讲,你可以把应用函数看作类似于函数的函数。 纯函数f可以通过执行f <$> x <*> y <*> z来提升。

与monad相比,applicative仿函数不能有select地运行它的参数。 所有争论的副作用将发生。

 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] 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库。

请参阅应用函数的基础知识,由Bryan O'Sullivan 提供实际工作 。