Haskell 与应用程序相比,Monad给了我们什么优势?
我读过,但不明白最后一节 作者说Monad提供了上下文敏感度,但仅使用一个应用实例就可以实现相同的结果:Haskell 与应用程序相比,Monad给了我们什么优势?,haskell,functional-programming,monads,applicative,Haskell,Functional Programming,Monads,Applicative,我读过,但不明白最后一节 作者说Monad提供了上下文敏感度,但仅使用一个应用实例就可以实现相同的结果: let maybeAge = (\futureYear birthYear -> if futureYear < birthYear then yearDiff birthYear futureYear else yearDiff futureYear birthYear) <$> (readMay futureYearString) <*>
let maybeAge = (\futureYear birthYear -> if futureYear < birthYear
then yearDiff birthYear futureYear
else yearDiff futureYear birthYear) <$> (readMay futureYearString) <*> (readMay birthYearString)
let maybeAge=(\futureYear birthy->if futureYear
没有do语法肯定更难看,但除此之外,我不明白为什么我们需要Monad。有人能帮我澄清一下吗?对于单子,后续效果可能取决于先前的值。例如,您可以有:
main = do
b <- readLn :: IO Bool
if b
then fireMissiles
else return ()
main=do
b,这个例子实际上没有使用上下文敏感性。考虑上下文敏感性的一种方法是,它允许用户根据一元值选择要采取的操作。在某种意义上,应用计算必须始终具有相同的“形状”,无论涉及的值如何;一元计算不需要。我个人认为用一个具体的例子更容易理解,所以让我们来看一个。下面是一个简单程序的两个版本,它要求您输入密码,检查您是否输入了正确的密码,并根据您是否输入了密码打印出响应
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
到目前为止,一切顺利。但如果我们使用应用程序版本:
*Main> checkPasswordA
What's the password?
hunter2
Correct. The secret answer is 42.
INTRUDER ALERT! INTRUDER ALERT!
我们输入了错误的密码,但我们仍然得到了秘密!还有入侵者警报!这是因为
和
,或者等效地liftAn
/liftMn
,总是执行其所有参数的效果。应用程序版本在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
或相当于
liftAN f app1 app2 ... appN
考虑这个问题,考虑<代码>应用程序< /C>的方法:
pure :: a -> f a
(<$>) :: (a -> b) -> f a -> f b
(<*>) :: f (a -> b) -> f a -> f b
(请记住,您只需要其中一个。)
手工操作很多,如果你仔细想想的话,我们能把应用程序函数组合在一起的唯一方法就是构造f app1形式的链。。。appN
,并可能嵌套这些链(例如,f(gxy)z
)。然而,(==)
)允许我们获取一个值,并根据该值生成不同的一元计算,可以动态构造。这就是我们用来决定是计算“打印出秘密”还是计算“打印出入侵者警报”的原因,以及为什么我们不能单独使用应用程序函子来做出决定;应用程序函数的任何类型都不允许使用普通值
您可以以类似的方式考虑join
与fmap
协同工作:,您可以执行以下操作
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)
这与以前的monadic版本做了相同的事情(只要我们先导入Control.Monad
以获得join
):
这里有两个使用
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)
您不能使用Applicative
接口实现它们。但为了开悟,让我们试着看看哪里出了问题。那
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
这是你对差异的第一个暗示。您不能仅使用复制ifM
的Applicative
接口编写函数
如果您将其分为将fa
形式的值看作是关于“效果”和“结果”(这两个术语都是非常模糊的近似术语,是可用的最佳术语,但不是很好),您可以在这里改进您的理解。对于类型为的值,可能是a
,作为计算,“效果”是成功还是失败。“结果”是一个类型为a
的值,在计算完成时可能会出现该值。(这些术语的含义在很大程度上取决于具体的类型,因此不要认为这是除了作为类型的可能
之外的有效描述。)
在这样的背景下,我们可以更深入地观察差异。Applicative
界面允许“结果”控制流是动态的,但它要求“效果”控制流是静态的。如果表达式包含3个可能失败的计算,则其中任何一个的失败都会导致整个计算失败。Monad
界面更加灵活。它允许“效果”控制流取决于“结果”值<代码>ifM根据第一个参数选择要包含在其自身“效果”中的参数的“效果”。这就是ifA
和ifM
之间巨大的根本区别
whileM
发生了更严重的事情。让我们试着做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)
whileA::Applicative f=>(a->f Bool)->(a->f a)->a->f a
whileA p step x=ifA(px)(whileA p step x)(纯x)
嗯。。发生的是编译错误<代码>()的类型不正确<代码>而p步骤的类型为a->fa
,步骤x
的类型为fa
<代码>()的形状不适合将它们组合在一起。要使其工作,函数类型必须是f(a->a)
你可以尝试更多的东西,但是你最终会发现whileA
没有一个实现可以像whileM
那样工作。我的意思是,您可以实现该类型,但无法使其同时循环和终止
要使其正常工作,需要加入
或(>>=)
。(好的,或者是其中一个的众多等价物之一)以及从Monad
界面中获得的额外内容。如果您尝试将Monad的bind
和Applicative
的类型签名转换为自然语言,您会发现:
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)
bind
:I将为您提供包含的值,您将返回一个新的打包值
:你给我一个打包的函数
checkPasswordM' :: IO ()
checkPasswordM' = join checkPasswordA'
*Main> checkPasswordM'
What's the password?
12345
INTRUDER ALERT! INTRUDER ALERT!
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)
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
*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
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)
Left e1 >> Left e2 === Left e1
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)
> Left "a" <*> Left "b"
Left 'a'
> Error "a" <*> Error "b"
Error "ab"
do
r1 <- act1
if r1
then act2
else act3