[英]What advantage does Monad give us over an Applicative?
I've read this article , but didn't understand last section. 我已经阅读了这篇文章 ,但不了解上一节。
The author says that Monad gives us context sensitivity, but it's possible to achieve the same result using only an Applicative instance: 作者说Monad给了我们上下文敏感性,但是仅使用一个Applicative实例就可以达到相同的结果:
let maybeAge = (\futureYear birthYear -> if futureYear < birthYear
then yearDiff birthYear futureYear
else yearDiff futureYear birthYear) <$> (readMay futureYearString) <*> (readMay birthYearString)
It's uglier for sure without do-syntax, but beside that I don't see why we need Monad. 如果没有do语法,这肯定很丑陋,但是除此之外,我不明白为什么我们需要Monad。 Can anyone clear this up for me? 谁能为我解决这个问题?
Here's a couple of functions that use the Monad
interface. 这是使用Monad
界面的几个函数。
ifM :: Monad m => m Bool -> m a -> m a -> m a
ifM c x y = c >>= \z -> if z then x else y
whileM :: Monad m => (a -> m Bool) -> (a -> m a) -> a -> m a
whileM p step x = ifM (p x) (step x >>= whileM p step) (return x)
You can't implement them with the Applicative
interface. 您无法使用Applicative
接口实现它们。 But for the sake of enlightenment, let's try and see where things go wrong. 但是为了启发,让我们尝试看看问题出在哪里。 How about.. 怎么样..
import Control.Applicative
ifA :: Applicative f => f Bool -> f a -> f a -> f a
ifA c x y = (\c' x' y' -> if c' then x' else y') <$> c <*> x <*> y
Looks good! 看起来不错! It has the right type, it must be the same thing! 它具有正确的类型,必须是同一件事! Let's just check to make sure.. 让我们检查一下以确保..
*Main> ifM (Just True) (Just 1) (Just 2)
Just 1
*Main> ifM (Just True) (Just 1) (Nothing)
Just 1
*Main> ifA (Just True) (Just 1) (Just 2)
Just 1
*Main> ifA (Just True) (Just 1) (Nothing)
Nothing
And there's your first hint at the difference. 您的第一个暗示就是区别。 You can't write a function using just the Applicative
interface that replicates ifM
. 您不能仅使用复制ifM
的Applicative
接口编写函数。
If you divide this up into thinking about values of the form fa
as being about "effects" and "results" (both of which are very fuzzy approximate terms that are the best terms available, but not very good), you can improve your understanding here. 如果您将这种思考形式分为fa
值和“结果”(两者都是非常模糊的近似术语,它们是可用的最佳术语,但不是很好),则可以提高您的理解这里。 In the case of values of type Maybe a
, the "effect" is success or failure, as a computation. 对于类型Maybe a
的值,作为计算,“效果”是成功还是失败。 The "result" is a value of type a
that might be present when the computation completes. “结果”是类型a
的值,计算完成后可能会出现。 (The meanings of these terms depends heavily on the concrete type, so don't think this is a valid description of anything other than Maybe
as a type.) (这些术语的含义在很大程度上取决于具体的类型,因此,除了Maybe
作为类型之外,不要认为这是对任何其他内容的有效描述。)
Given that setting, we can look at the difference in a bit more depth. 在此设置下,我们可以更深入地了解差异。 The Applicative
interface allows the "result" control flow to be dynamic, but it requires the "effect" control flow to be static. Applicative
接口允许“结果”控制流是动态的,但是它要求“效果”控制流是静态的。 If your expression involves 3 computations that can fail, the failure of any one of them causes the failure of the whole computation. 如果您的表达式涉及3个可能失败的计算,则其中任何一个失败都会导致整个计算失败。 The Monad
interface is more flexible. Monad
界面更加灵活。 It allows the "effect" control flow to depend on the "result" values. 它允许“效果”控制流取决于“结果”值。 ifM
chooses which argument's "effects" to include in its own "effects" based on its first argument. ifM
根据第一个参数选择将哪个参数的“效果”包括在自己的“效果”中。 This is the huge fundamental difference between ifA
and ifM
. 这之间的巨大的根本区别ifA
和ifM
。
There's something even more serious going on with whileM
. whileM
发生了一些更严重的事情。 Let's try to make whileA
and see what happens. 让我们尝试使whileA
看看会发生什么。
whileA :: Applicative f => (a -> f Bool) -> (a -> f a) -> a -> f a
whileA p step x = ifA (p x) (whileA p step <*> step x) (pure x)
Well.. What happens is a compile error. 好吧..发生的是编译错误。 (<*>)
doesn't have the right type there. (<*>)
在此处没有正确的类型。 whileA p step
has the type a -> fa
and step x
has the type fa
. whileA p step
的类型a -> fa
whileA p step
a -> fa
而step x
的类型为fa
。 (<*>)
isn't the right shape to fit them together. (<*>)
不是将它们组合在一起的正确形状。 For it to work, the function type would need to be f (a -> a)
. 为了使其正常工作,函数类型必须为f (a -> a)
。
You can try lots more things - but you'll eventually find that whileA
has no implementation that works anything even close to the way whileM
does. 你可以尝试很多更多的东西-但你最终会发现, whileA
没有实现,甚至接近任何工作的方式whileM
一样。 I mean, you can implement the type, but there's just no way to make it both loop and terminate. 我的意思是,您可以实现类型,但是没有办法使其同时循环和终止。
Making it work requires either join
or (>>=)
. 要使其正常工作, 需要 join
或(>>=)
。 (Well, or one of the many equivalents of one of those) And those the extra things you get out of the Monad
interface. (好吧,或者是其中一种的等价产品之一),这些是您从Monad
界面中获得的额外Monad
。
With monads, subsequent effects can depend on previous values. 对于单声道,后续效果可能取决于先前的值。 For example, you can have: 例如,您可以拥有:
main = do
b <- readLn :: IO Bool
if b
then fireMissiles
else return ()
You can't do that with Applicative
s - the result value of one effectfull computation can't determine what effect will follow. 您无法使用Applicative
s做到这一点-一个effectfull计算的结果值无法确定将要出现的效果。
Somewhat related: 有点相关:
As Stephen Tetley said in a comment , that example doesn't actually use context-sensitivity. 正如斯蒂芬·泰特利(Stephen Tetley)在评论中说的那样 ,该示例实际上并未使用上下文相关性。 One way to think about context-sensitivity is that it lets use choose which actions to take depending on monadic values. 思考上下文敏感度的一种方法是,它可以根据单子值选择使用哪种动作。 Applicative computations must always have the same "shape", in a certain sense, regardless of the values involved; 在某种意义上,无论涉及的值如何,应用计算都必须始终具有相同的“形状”。 monadic computations need not. 单子计算不需要。 I personally think this is easier to understand with a concrete example, so let's look at one. 我个人认为,通过一个具体示例更容易理解这一点,因此让我们来看一个示例。 Here's two versions of a simple program which ask you to enter a password, check that you entered the right one, and print out a response depending on whether or not you did. 这是一个简单程序的两个版本,要求您输入密码,检查是否输入了正确的密码,并根据您是否输入了答案来打印出来。
import Control.Applicative
checkPasswordM :: IO ()
checkPasswordM = do putStrLn "What's the password?"
pass <- getLine
if pass == "swordfish"
then putStrLn "Correct. The secret answer is 42."
else putStrLn "INTRUDER ALERT! INTRUDER ALERT!"
checkPasswordA :: IO ()
checkPasswordA = if' . (== "swordfish")
<$> (putStrLn "What's the password?" *> getLine)
<*> putStrLn "Correct. The secret answer is 42."
<*> putStrLn "INTRUDER ALERT! INTRUDER ALERT!"
if' :: Bool -> a -> a -> a
if' True t _ = t
if' False _ f = f
Let's load this into GHCi and check what happens with the monadic version: 让我们将其加载到GHCi中,并检查Monadic版本会发生什么:
*Main> checkPasswordM
What's the password?
swordfish
Correct. The secret answer is 42.
*Main> checkPasswordM
What's the password?
zvbxrpl
INTRUDER ALERT! INTRUDER ALERT!
So far, so good. 到目前为止,一切都很好。 But if we use the applicative version: 但是,如果我们使用适用版本:
*Main> checkPasswordA
What's the password?
hunter2
Correct. The secret answer is 42.
INTRUDER ALERT! INTRUDER ALERT!
We entered the wrong password, but we still got the secret! 我们输入了错误的密码,但仍然有秘密! And an intruder alert! 还有入侵者警报! This is because <$>
and <*>
, or equivalently liftA n
/ liftM n
, always execute the effects of all their arguments. 这是因为<$>
和<*>
或等效的liftA n
/ liftM n
始终执行其所有参数的效果。 The applicative version translates, in do
notation, to 应用型版本翻译,在do
记号,以
do pass <- putStrLn "What's the password?" *> getLine)
unit1 <- putStrLn "Correct. The secret answer is 42."
unit2 <- putStrLn "INTRUDER ALERT! INTRUDER ALERT!"
pure $ if' (pass == "swordfish") unit1 unit2
And it should be clear why this has the wrong behavior. 并且应该清楚为什么这会有错误的行为。 In fact, every use of applicative functors is equivalent to monadic code of the form 实际上,应用函子的每次使用都等同于形式的单子代码
do val1 <- app1
val2 <- app2
...
valN <- appN
pure $ f val1 val2 ... valN
(where some of the appI
are allowed to be of the form pure xI
). (其中某些appI
可以采用pure xI
的形式)。 And equivalently, any monadic code in that form can be rewritten as 同样,该形式的任何单子代码都可以重写为
f <$> app1 <*> app2 <*> ... <*> appN
or equivalently as 或等同于
liftAN f app1 app2 ... appN
To think about this, consider Applicative
's methods: 考虑这一点,请考虑Applicative
的方法:
pure :: a -> f a
(<$>) :: (a -> b) -> f a -> f b
(<*>) :: f (a -> b) -> f a -> f b
And then consider what Monad
adds: 然后考虑Monad
添加的内容:
(=<<) :: (a -> m b) -> m a -> m b
join :: m (m a) -> m a
(Remember that you only need one of those.) (请记住,您只需要其中之一。)
Handwaving a lot, if you think about it, the only way we can put together the applicative functions is to construct chains of the form f <$> app1 <*> ... <*> appN
, and possibly nest those chains ( eg , f <$> (g <$> x <*> y) <*> z
). 如果想一想,会花很多时间,我们将应用功能组合在一起的唯一方法是构造形式为f <$> app1 <*> ... <*> appN
链,并可能嵌套这些链( 例如 , f <$> (g <$> x <*> y) <*> z
)。 However, (=<<)
(or (>>=)
) allows us to take a value and produce different monadic computations depending on that value, that could be constructed on the fly. 但是, (=<<)
(或(>>=)
)允许我们获取一个值并根据该值产生不同的单子计算,这些计算可以随时进行构造。 This is what we use to decide whether to compute "print out the secret", or compute "print out an intruder alert", and why we can't make that decision with applicative functors alone; 这就是我们用来决定是计算“打印出秘密”还是计算“打印出入侵者警报”的原因,以及为什么我们不能仅凭应用函子来做出该决定; none of the types for applicative functions allow you to consume a plain value. 应用函数的所有类型均不允许您使用纯值。
You can think about join
in concert with fmap
in a similar way: as I mentioned in a comment , you can do something like 您可以考虑以类似的方式与fmap
一起join
: 正如我在评论中提到的 ,您可以执行以下操作
checkPasswordFn :: String -> IO ()
checkPasswordFn pass = if pass == "swordfish"
then putStrLn "Correct. The secret answer is 42."
else putStrLn "INTRUDER ALERT! INTRUDER ALERT!"
checkPasswordA' :: IO (IO ())
checkPasswordA' = checkPasswordFn <$> (putStrLn "What's the password?" *> getLine)
This is what happens when we want to pick a different computation depending on the value, but only have applicative functionality available us. 当我们想根据值选择不同的计算,但只有可用的应用功能可用时,就会发生这种情况。 We can pick two different computations to return, but they're wrapped inside the outer layer of the applicative functor. 我们可以选择两个不同的计算来返回,但它们被包裹在应用函子的外层之内。 To actually use the computation we've picked, we need join
: 要实际使用我们选择的计算,我们需要join
:
checkPasswordM' :: IO ()
checkPasswordM' = join checkPasswordA'
And this does the same thing as the previous monadic version (as long as we import Control.Monad
first, to get join
): 这与以前的monadic版本具有相同的作用(只要我们首先import Control.Monad
即可获得join
):
*Main> checkPasswordM'
What's the password?
12345
INTRUDER ALERT! INTRUDER ALERT!
On the other hand, here's aa practical example of the Applicative
/ Monad
divide where Applicative
s have an advantage: error handling! 另一方面,这是一个Applicative
/ Monad
除法的实际示例,其中Applicative
具有优势:错误处理! We clearly have a Monad
implementation of Either
that carries along errors, but it always terminates early. 我们显然有一个Either
的Monad
实现,该实现带有错误,但始终会提前终止。
Left e1 >> Left e2 === Left e1
You can think of this as an effect of intermingling values and contexts. 您可以认为这是将值和上下文混合在一起的结果。 Since (>>=)
will try to pass the result of the Either ea
value to a function like a -> Either eb
, it must fail immediately if the input Either
is Left
. 由于(>>=)
会尝试将Either ea
值的结果传递给Either ea
a -> Either eb
类a -> Either eb
函数,因此如果输入Either
为Left
,它必须立即失败。
Applicative
s only pass their values to the final pure computation after running all of the effects. Applicative
s在运行所有效果后才将其值传递给最终的纯计算。 This means they can delay accessing the values for longer and we can write this. 这意味着他们可以延迟访问值更长的时间,我们可以编写它。
data AllErrors e a = Error e | Pure a deriving (Functor)
instance Monoid e => Applicative (AllErrors e) where
pure = Pure
(Pure f) <*> (Pure x) = Pure (f x)
(Error e) <*> (Pure _) = Error e
(Pure _) <*> (Error e) = Error e
-- This is the non-Monadic case
(Error e1) <*> (Error e2) = Error (e1 <> e2)
It's impossible to write a Monad
instance for AllErrors
such that ap
matches (<*>)
because (<*>)
takes advantage of running both the first and second contexts before using any values in order to get both errors and (<>)
them together. 不可能为AllErrors
编写Monad
实例, AllErrors
使ap
匹配(<*>)
因为(<*>)
会在使用任何值之前同时运行第一个和第二个上下文,以便同时获取错误和(<>)
它们一起。 Monad
ic (>>=)
and (join)
can only access contexts interwoven with their values. Monad
ic (>>=)
和(join)
只能访问与其值交织的上下文。 That's why Either
's Applicative
instance is left-biased, so that it can also have a harmonious Monad
instance. 这就是Either
的Applicative
实例偏左的原因,以便它也可以具有一个和谐的Monad
实例。
> Left "a" <*> Left "b"
Left 'a'
> Error "a" <*> Error "b"
Error "ab"
With Applicative, the sequence of effectful actions to be performed is fixed at compile-time. 使用Applicative,将要执行的有效动作的顺序在编译时固定。 With Monad, it can be varied at run-time based on the results of effects. 使用Monad,可以在运行时根据效果的结果对其进行更改。
For example, with an Applicative parser, the sequence of parsing actions is fixed for all time. 例如,对于Applicative解析器,解析动作的顺序在任何时候都是固定的。 That means that you can potentially perform "optimisations" on it. 这意味着您可以对它进行“优化”。 On the other hand, I can write a Monadic parser which parses some a BNF grammar description, dynamically constructs a parser for that grammar, and then runs that parser over the rest of the input. 另一方面,我可以编写一个Monadic解析器,该解析器解析一些BNF语法描述,为该语法动态构造一个解析器,然后在其余输入上运行该解析器。 Every time you run this parser, it potentially constructs a brand new parser to parse the second portion of the input. 每次运行此解析器时,它都可能构造一个全新的解析器来解析输入的第二部分。 Applicative has no hope of doing such a thing - and there is no chance of performing compile-time optimisations on a parser that doesn't exist yet... Applicative没有希望做这样的事情-并且没有机会在尚不存在的解析器上执行编译时优化...
As you can see, sometimes the "limitation" of Applicative is actually beneficial - and sometimes the extra power offered by Monad is required to get the job done. 如您所见,有时Applicative的“局限性”是有益的-有时Monad提供的额外功能才能完成工作。 This is why we have both. 这就是为什么我们两者都有的原因。
If you try to convert the type signature of Monad's bind
and Applicative <*>
to natural language, you will find that: 如果您尝试将Monad的bind
和Applicative <*>
的类型签名转换为自然语言,则会发现:
bind
: I will give you the contained value and you will return me a new packaged value bind
: 我将给您包含的值, 您将给我返回一个新的打包值
<*>
: You give me a packaged function that accepts a contained value and return a value and I will use it to create new packaged value based on my rules. <*>
: 您给了我一个打包的函数,该函数接受一个包含的值并返回一个值, 我将使用它根据我的规则创建新的打包值。
Now as you can see from the above description, bind
gives you more control as compared to <*>
现在,从上面的描述中您可以看到, bind
<*>
相比, bind
给您更多的控制权
If you work with Applicatives, the "shape" of the result is already determined by the "shape" of the input, eg if you call [f,g,h] <*> [a,b,c,d,e]
, your result will be a list of 15 elements, regardless which values the variables have. 如果您使用的是Applicatives,则结果的“形状”已经由输入的“形状”确定,例如,如果您调用[f,g,h] <*> [a,b,c,d,e]
,则结果将是15个元素的列表,无论变量具有哪个值。 You don't have this guarantee/limitation with monads. 您没有monad的这种保证/限制。 Consider [x,y,z] >>= join replicate
: For [0,0,0]
you'll get the result []
, for [1,2,3]
the result [1,2,2,3,3,3]
. 考虑[x,y,z] >>= join replicate
[1,2,2,3,3,3]
:对于[0,0,0]
您将获得结果[]
;对于[1,2,3]
,您将获得结果[1,2,2,3,3,3]
。
Now that ApplicativeDo
extension become pretty common thing, the difference between Monad
and Applicative
can be illustrated using simple code snippet. 现在, ApplicativeDo
扩展已经很普遍了,可以使用简单的代码片段说明Monad
与Applicative
之间的区别。
With Monad
you can do 有了Monad
您可以做到
do
r1 <- act1
if r1
then act2
else act3
but having only Applicative
do-block, you can't use if
on things you've pulled out with <-
. 但是只有Applicative
do-block, if
您使用<-
拔出了东西,则无法使用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.