如何证明一个单子是一个函子和应用函子?

15

单子在理论上被认为是函子的一个子集,特别是应用函子,即使这在Haskell的类型系统中没有体现。

基于此,给定一个单子并且根据returnbind,如何:

  • 推导出fmap
  • 推导出<*>
2个回答

26

嗯,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提供完整的FunctorApplicative实例:

instance Functor M where
  fmap = liftM

instance Applicative M where
  pure = return
  (<*>) = ap
理想情况下,我们应该能够直接在Monad中指定这些实现,以减轻为每个monad定义单独实例的负担,例如使用此建议。 如果发生这种情况,则没有真正的障碍使Applicative成为Monad的超类,因为它将确保不会破坏任何现有代码。 另一方面,这意味着为给定的Monad定义FunctorApplicative实例所涉及的样板很少,因此很容易成为“好公民”(并且应为任何monad定义这样的实例)。

5
这个答案缺少一个重要的部分:证明如果给定的Monad实例m确实满足Monad法则,那么你为fmap、pure和(<*>)提供的单子定义将遵守Functor和Applicative法则。 Haskell强制执行的只是类型检查。 - Luis Casillas

10

fmap = liftM,以及 (<*>) = ap。这里是 liftMap 的源代码链接。我假设你知道如何去糖化 do 符号。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接