3

人们说 monad 是应用函子的扩展,但我不这么认为。让我们举一个应用函子的例子:(<*>) :: f(a->b) -> f a -> f b

[(+3)] <*> [2,3,4]

现在,我也希望我可以做和 monad 一样的事情,这意味着我可以应用 2 个参数:一个上下文包含一个函数,另一个上下文来获取一个上下文。但对于 monad,我不能。我所需要的只是编写一个像这样丑陋的函数:

[2,3,4] >>= (\x->[x+3])

是的,当然,你可以说这[(+3)]等价于[\x->(x+3)]. 但至少,这个函数是在上下文中的。

最后,我在这里看不到等价或扩展。Monad 是一种不同的风格,在另一个故事中很有用。

对不起我的无知。

4

3 回答 3

12

如果T是 的一个实例Monad,那么您可以使它成为这样的一个实例Applicative

instance Functor T where
    fmap = liftM

instance Applicative T where
    pure = return
    (<*>) = ap

liftM定义为

liftM   :: (Monad m) => (a1 -> r) -> m a1 -> m r
liftM f m1              = do { x1 <- m1; return (f x1) }

ap定义为

ap                :: (Monad m) => m (a -> b) -> m a -> m b
ap                =  liftM2 id

liftM2  :: (Monad m) => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
liftM2 f m1 m2          = do { x1 <- m1; x2 <- m2; return (f x1 x2) }

因此,“单子是应用函子的扩展”,因为任何单子都可以成为应用函子。实际上,在标准库中广泛(不是普遍)认为 classMonad不是从 class 派生的错误Applicative

于 2012-11-23T17:56:31.140 回答
9
import Control.Applicative

我认为它澄清了<*>再次定义的关系,但使用了 Monad:

(>*>) :: Monad m => m (a -> b) -> m a -> m b
mf >*> ma = do
   f <- mf
   a <- ma
   return (f a)

给出与以下相同的结果<*>

*Main> [(+3)] >*> [2,3,4]
[5,6,7]
*Main> [(+3)] <*> [2,3,4]
[5,6,7]

甚至

*Main> [(+3),(*10)] <*> [2,3,4]
[5,6,7,20,30,40]
*Main> [(+3),(*10)] >*> [2,3,4]
[5,6,7,20,30,40]

现在,变量fa定义中最后一行的存在是Monad 和 Applicative 之间>*>关键区别。在 Applicative 中,你只能return在末尾做一些事情,而在 Monad 中,你可以用fand做任何你喜欢的事情a

相似之处

在应用程序中,你可以做

getNonEmptyStringA :: IO String
getNonEmptyStringA = (:) <$> getChar <*> getLine

我们可以将其转换为 Monad 函数

getNonEmptyStringM' = (:) `fmap` getChar >*> getLine

或更典型地,

getNonEmptyStringM :: IO String
getNonEmptyStringM = do
    c <- getChar
    xs <- getLine
    return (c:xs)

区别

在 Monad 你可以做

checkFirst :: IO (Maybe String)
checkFirst = do
    c <- getChar
    if c == 'n' then return Nothing
                else fmap Just getLine

例如,

Main> checkFirst >>= print
qwerty
Just "werty"

Main> checkFirst >>= print
nNothing

请注意,在我输入- 它checkFirst之后发生的事情发生了变化,它立即返回,而我没有机会输入或按 Enter 键,而如果我从它开始,它会继续运行。这种根据的强度改变所做工作的能力是 Monad 和 Applicative 之间的主要区别,但是您可以通过运算符看到 Monad 完成了 Applicative 所做的一切。(它们都有,Applicative 调用它们,它们都有or因为它们都是 Functor。)nNothinggetLineqgetLine>*>returnpure(<$>)fmap

checkFirst您可以在 Applicative 中最接近写作的是

don'tCheckFirst :: IO (Maybe String)
don'tCheckFirst = check <$> getChar <*> getLine  where
   check c xs = if c == 'n' then Nothing
                else Just (c:xs)

像这样工作:

Main> don'tCheckFirst >>= print
nI can keep typing because it has to do the getLine anyway
Nothing

Main> don'tCheckFirst >>= print
qwerty
Just "qwerty"

(注意:由于getChar 中的 Windows ghc 错误,您无法区分windows 中的 ghcicheckFirst和in 。)don'tCheckFirst

概括

Monad 类似于 Applicative,但能够根据存在的值完全改变你正在做的事情。

于 2012-11-23T18:57:28.477 回答
6

Haskell 中的 monad 是 Applicative plus join,即“扁平化” monad 的函数join :: m (m a) -> m a

“应用程序”<*>具有类型f (a -> b) -> f a -> f b;如果您现在选择b在同一个 Functor 中的类型,即b :: f c类型签名专门用于<*> :: f (a -> f c) -> f a -> f (f c). 当你没有一元结构时,你就完了;但是,使用 monadicjoin函数,您可以使结果变平,在与以前相同的 monad 中得到一些东西(而不是双堆叠的 monad)。

于 2012-11-23T22:58:53.320 回答