fmap和bind之间的能力差异?

时间:2016-02-14 01:04:43

标签: haskell functional-programming monads functor

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

函子:

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

Monad(简化):

class Monad m where
    (>>=) :: m a -> (a -> m b) -> m b
  • fmap接受函数和仿函数,并返回一个仿函数。
  • >>=接受一个函数和一个monad,并返回一个monad。

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

  • fmap - (a -> b)
  • >>= - (a -> m b)

>>=接受一个返回monad的函数参数。我知道这很重要,但是我很难看到这一点轻微的东西使monad比functor强大得多。有人可以解释一下吗?

3 个答案:

答案 0 :(得分:14)

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

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

现在相当明显:使用bind函数,我们应用一个返回b y而不是y的函数。那会有什么不同呢?

考虑这个小例子:

foo <$> Just 3

请注意,(<$>)会将foo应用于3,并将结果放回Just。换句话说,此计算的结果不能Nothing。相反:

bar =<< Just 3

此计算可以返回Nothing。 (例如,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),结果的“结构”始终与输入相同。但是使用(=<<)(即(>>=)),结果的结构可能会发生变化。这允许条件执行,对输入做出反应以及其他一系列事情。

答案 1 :(得分:9)

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

我认为最令人困惑的是Functors的所有常见示例(例如ListMaybeIO)也是Monads。我们需要一个像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的{​​{1}}和来自Event timeB Event f a的结果Event。我们的LocalTime类型定义为只有一个timeLocalTime),因此无需转换两个personalPhoto即可使Monad无法使用合而为一。 (在某些情况下,这样做可能有意义,所以如果你真的想这么做就可以把它变成一个monad。)

答案 2 :(得分:3)

假设IO只是Functor,而不是Monad。我们如何对两个动作进行排序?比方说,如getChar :: IO CharputChar :: Char -> IO ()

我们可以尝试使用getChar映射Char(执行时,从stdin读取putChar的操作。

fmap putChar getChar :: IO (IO ())

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

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

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

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

所有这些与>>=有什么关系?碰巧,monadic绑定只是fmapjoin的组合:

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

另一种看待差异的方法是fmap永远不会更改映射值的整体结构,但join(以及>>=也可以这样做。

IO行为而言,fmap 永远不会导致上瘾的读/写或其他影响。但是join将内部动作的读/写顺序排列在外部动作之后。