单子在理论上被认为是函子的一个子集,特别是应用函子,即使这在Haskell的类型系统中没有体现。
基于此,给定一个单子并且根据return
和bind
,如何:
- 推导出
fmap
, - 推导出
<*>
?
单子在理论上被认为是函子的一个子集,特别是应用函子,即使这在Haskell的类型系统中没有体现。
基于此,给定一个单子并且根据return
和bind
,如何:
fmap
,<*>
?嗯,fmap
只是 (a -> b) -> f a -> f b
,也就是说我们希望使用一个纯函数转换单子操作的结果。这很容易用 do 语法来写:
fmap f m = do
a <- m
return (f a)
或者,以"原始格式"写成:
fmap f m = m >>= \a -> return (f a)
这个函数可以通过Control.Monad.liftM
来使用。
pure :: a -> f a
当然就是return
。而(<*>) :: f (a -> b) -> f a -> f b
就稍微有点棘手了。我们有一个返回函数的操作,和一个返回其参数的操作,我们希望得到一个返回结果的操作。再次使用do符号表示:
mf <*> mx = do
f <- mf
x <- mx
return (f x)
或者,展开写成:
mf <*> mx =
mf >>= \f ->
mx >>= \x ->
return (f x)
哒哒哒!这个函数可以在Control.Monad.ap
找到,因此我们可以按照以下方式为任何单子M
提供完整的Functor
和Applicative
实例:
instance Functor M where
fmap = liftM
instance Applicative M where
pure = return
(<*>) = ap
理想情况下,我们应该能够直接在Monad
中指定这些实现,以减轻为每个monad定义单独实例的负担,例如使用此建议。 如果发生这种情况,则没有真正的障碍使Applicative
成为Monad
的超类,因为它将确保不会破坏任何现有代码。 另一方面,这意味着为给定的Monad
定义Functor
和Applicative
实例所涉及的样板很少,因此很容易成为“好公民”(并且应为任何monad定义这样的实例)。