fmap 和 bind 之间的能力差异?

Difference in capability between fmap and bind?

我是函数式编程的新手(来自 javascript),我很难说出两者之间的区别,这也扰乱了我对仿函数与 monad 的理解。

仿函数:

class Functor f where
    fmap :: (a -> b) -> f a -> f b

Monad(简体):

class Monad m where
    (>>=) :: m a -> (a -> m b) -> m b

两者的区别在于函数参数:

>>= 接受一个函数参数,returns 一个 monad。我知道这很重要,但我很难看出这一点小事如何使 monad 比函子更强大。有人可以解释一下吗?

简短的回答是,如果你能以一种有意义的方式将 m (m a) 变成 m a,那么它就是一个 Monad。这对所有 Monad 都是可能的,但不一定对 Functors。

我认为最令人困惑的是 Functor 的所有常见示例(例如 ListMaybeIO)也是 Monad。我们需要一个 Functor 而不是 Monad 的例子。

我将使用假设的日历程序中的示例。以下代码定义了一个 Event Functor,它存储了一些与事件相关的数据以及事件发生的时间。

import Data.Time.LocalTime

data Event a = MkEvent LocalTime a

instance Functor Event where
    fmap f (MkEvent time a) = MkEvent time (f a)

Event 对象存储事件发生的时间和一些可以使用 fmap 更改的额外数据。现在让我们尝试将其设为 Monad:

instance Monad Event where
    (>>=) (MkEvent timeA a) f = let (MkEvent timeB b) = f a in
                                MkEvent <notSureWhatToPutHere> b

我们发现我们做不到,因为您最终会得到两个 LocalTime 对象。 timeA 来自给定的 EventtimeB 来自 f a 的结果给出的 Event。我们的 Event 类型被定义为只有一个 LocalTime (time) 出现,所以如果不把两个 LocalTime 变成一个,就不可能把它变成 Monad . (在某些情况下这样做可能是有意义的,所以如果你真的想的话,你可以把它变成一个 monad)。

暂时假设 IO 只是 Functor,而不是 Monad。我们如何安排两个动作的顺序?比如 getChar :: IO CharputChar :: Char -> IO ().

我们可以尝试使用 putChar.

映射 getChar(执行时从标准输入读取 Char 的操作)
fmap putChar getChar :: IO (IO ())

现在我们有一个程序,它在执行时从 stdin 读取 Char 并生成一个程序,在执行时将 Char 写入 stdout。但我们真正想要的是一个程序,当执行时,从 stdin 读取 Char 并将 Char 写入 stdout。所以我们需要一个"flattening"(在IO情况下,"sequencing")函数,类型为:

join :: IO (IO ()) -> IO ()

Functor本身不提供这个功能。但它是 Monad 的函数,它具有更通用的类型:

join :: Monad m => m (m a) -> m a

这一切与>>=有什么关系?碰巧的是,monadic bind 只是 fmapjoin:

的组合
:t \m f -> join (fmap f m)
(Monad m) => m a1 -> (a1 -> m a) -> m a

看到差异的另一种方式是 fmap 永远不会改变映射值的整体结构,但是 join(因此 >>= 也可以)可以做到这一点。

IO 行为而言,fmap 永远不会 造成额外的 reads/writes 或其他影响。但是 join 将内部动作的 read/writes 排在外部动作之后。

嗯,(<$>)fmap 的别名,而 (=<<)(>>=) 相同,但参数交换:

(<$>) :: (x ->   y) -> b x -> b y
(=<<) :: (x -> b y) -> b x -> b y

区别现在相当明显:使用绑定函数,我们应用的函数 return 是 b y 而不是 y。那么这有什么区别呢?

考虑这个小例子:

foo <$> Just 3

请注意 (<$>) 会将 foo 应用于 3,并将结果放回 Just。也就是说,这个计算的结果不可能Nothing。反之:

bar =<< Just 3

这个计算可以returnNothing。 (例如,bar x = Nothing 即可。)

我们可以用列表 monad 做类似的事情:

foo <$> [Red, Yellow, Blue]   -- Result is guaranteed to be a 3-element list.
bar =<< [Red, Yellow, Blue]   -- Result can be ANY size.

简而言之,对于 (<$>)(即 fmap),结果的 "structure" 始终与输入相同。但是使用 (=<<)(即 (>>=)),结果的结构可以改变。这允许条件执行、对输入做出反应以及一大堆其他事情。