IO 是 Functor 的一个实例,因为它首先是一个 Monad 吗?

Enr*_*lis 3 monads haskell functor do-notation io-monad

LYAH我了解到这个do符号只是monadic风格的语法糖;从维基书中我读到的或多或少是一样的;所以我的理解是,do如果没有Monad实例,就不可能有任何符号。

然而,我阅读FunctorIO类型 ctor实例的这个定义。

instance Functor IO where  
    fmap f action = do  
        result <- action  
        return (f result)  
Run Code Online (Sandbox Code Playgroud)

这只是以下内容的语法糖,不是吗?

instance Functor IO where  
    fmap f action = action >>= return . f  
Run Code Online (Sandbox Code Playgroud)

这意味着下面的假设IOMonadfirst 的实例;这与每个Monad都是一个Functor而不是相反的事实背道而驰。

事实上,我已经意识到 aMonad是“多于” an Applicative,而后者又是“多于” a Functor,这与对其实例Applicative强制执行Functor约束Monad的定义(以及理想情况下的定义要求其实例是Applicatives,因为如果它不是就不要让它成为MonadApplicative)。

换句话说,上面的代码让我觉得没有办法为 编写Functor实例IO,如果IO首先不是 a Monad

现在想想,也许这就像说它IO是作为一个成熟的Monad.

但我很困惑,所以我在这里寻求帮助。

Jon*_*rdy 11

具有 实例的类型Monad意味着它必须具有Functor(和Applicative)的定义,但这并不意味着Functor必须“首先”定义该实例,只是两个实例都必须存在。只要方法实现不是循环定义的,就没有问题。

事实上,这通常是有意义的实施Monad为第一类,然后定义ApplicativeFunctor来讲机械Monad操作,因为Monad更强大,所以其他情况下都只是限制了的Monad实例:

-- These work for any T with a Monad instance.

instance Functor T where
  fmap f x = do
    x' <- x
    return (f x')

instance Applicative T where
  pure = return
  f <*> x = do
    f' <- f
    x' <- x
    return (f' x')
Run Code Online (Sandbox Code Playgroud)

这正是因为“aMonad是'比'an 多的东西Applicative,而后者又是'比'a 多的东西Functor”。

还值得注意的是,最初,MonadFunctor类是无关的,而Applicative类(后来添加)也是如此。有用于每个,如单独的等同的功能fmapliftAliftM; purereturn; (<*>)ap; traversemapM。按照惯例,您会编写一个Monad实例并据此实现其他类。这些单独的定义仍然存在,但现在是多余的:由于“Applicative-Monad Proposal”创建ApplicativeMonad, 和Functorof的超类Applicative,您始终可以使用 egpure代替returntraverse代替mapM,因为它们是相同的功能,但在更多的上下文中工作。所以还有一些历史背景,说明为什么可能对一个类型的这些实例有单独的定义。

正如@dfeuer 指出的那样,有些数据结构mapM( mapM_, forM, forM_) 比traverse(resp. traverse_, for, for_)更有效,例如Data.Vector类型。在向量的特定情况下,我相信这是因为该库可以利用 monadic 测序作为优化,以实现更多的流媒体和结果分配。但它们是等价的,因为traverse应该总是产生相同的结果。

  • 在一些不寻常的情况下,“mapM”可能比“traverse”更有效。我在野外所知道的一个是“向量”。单子流框架可以为“mapM”实现“traverse”无法实现的魔术。 (2认同)