我是函数式编程的新手(来自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强大得多。有人可以解释一下吗?
答案 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的所有常见示例(例如List
,Maybe
,IO
)也是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
类型定义为只有一个time
(LocalTime
),因此无需转换两个personalPhoto
即可使Monad无法使用合而为一。 (在某些情况下,这样做可能有意义,所以如果你真的想这么做就可以把它变成一个monad。)
答案 2 :(得分:3)
假设IO
只是Functor
,而不是Monad
。我们如何对两个动作进行排序?比方说,如getChar :: IO Char
和putChar :: 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绑定只是fmap
和join
的组合:
:t \m f -> join (fmap f m)
(Monad m) => m a1 -> (a1 -> m a) -> m a
另一种看待差异的方法是fmap
永远不会更改映射值的整体结构,但join
(以及>>=
也可以这样做。
就IO
行为而言,fmap
永远不会导致上瘾的读/写或其他影响。但是join
将内部动作的读/写顺序排列在外部动作之后。