【问题标题】:Is IO an instance of Functor just because it is a Monad in the first place?IO 是 Functor 的一个实例,仅仅是因为它首先是一个 Monad 吗?
【发布时间】:2020-11-12 12:32:10
【问题描述】:

来自LYAH 我了解到do 符号只是单子风格的语法糖;从wikibook 我读到的或多或少是一样的;所以我的理解是,如果没有 Monad 实例,就不可能有任何 do 符号。

然而,我阅读了Functor 类型 ctor 的 Functor 实例的定义。

instance Functor IO where  
    fmap f action = do  
        result <- action  
        return (f result)  

这只是下面的语法糖,不是吗?

instance Functor IO where  
    fmap f action = action >>= return . f  

这意味着IO首先是Monad的实例;这与每个Monad 都是Functor 而不是相反的事实相矛盾。

事实上,我已经意识到Monad 是“比”Applicative,而Functor 又是“比”Functor,它与 Applicative 的定义执行一起使用对其实例的Functor 约束(以及Monad 的定义理想情况下要求其实例为Applicatives,如如果不是Applicative,则不要将其设为Monad em>)。

换句话说,如果IO 不是Monad,那么上面的代码让我认为没有办法为IO 编写Functor 实例。

现在我想起来,也许这就像说IO 是作为一个成熟的Monad 创建的,而上面的实例后来只是为了完整性和数学一致性。

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

【问题讨论】:

    标签: haskell monads functor io-monad do-notation


    【解决方案1】:

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

    实际上,通常先为一个类型实现Monad,然后根据Monad操作机械地定义ApplicativeFunctor,因为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')
    

    这正是因为Monad‘比’Applicative‘比’Functor‘更重要’”。

    还值得注意的是,最初,MonadFunctor 类是不相关的,Applicative 类(后来添加)也是如此。每个都有单独的等效函数,例如 fmapliftAliftMpurereturn(&lt;*&gt;)aptraversemapM。按照惯例,您会编写一个Monad 实例并以此实现其他类。这些单独的定义仍然存在,但现在是多余的:因为“Applicative–Monad 提案”使Applicative 成为Monad 的超类,以及FunctorApplicative,你可以总是使用例如pure 而不是 returntraverse 而不是 mapM,因为它们是相同的功能,但在严格的更多上下文中工作。所以也有一些历史背景来解释为什么一个类型的这些实例可能会有不同的定义。

    正如@dfeuer 指出的那样,在某些数据结构中,mapMmapM_forMforM_)比traverse(分别是traverse_for、@ 987654363@),如Data.Vector 类型。在向量的特定情况下,我相信这是因为库可以利用单子排序作为优化,以实现更多的流式传输和分配结果。但它们是等价的,traverse 应该总是产生相同的结果。

    【讨论】:

    • +1。所以当你写其他实例只是限制Monad实例时,你的意思是有一种方法可以使用Monad实例提供的工具(例如&gt;&gt;=return,或者 do 表示法)来编写另外两个的实例,但不能反过来,例如,我无法将类型 ctor 设为 Monad 的实例,只是依靠在fmap 和/或&lt;*&gt;pure。这是正确的吗?
    • @EnricoMariaDeAngelis:正确!特别是,Monad 为您提供一元应用程序(=&lt;&lt;) :: (a -&gt; f b) -&gt; f a -&gt; f b,其中第一个操作数的f 效果可能取决于第二个操作数的a 结果的。你不能用Applicative 应用程序(&lt;*&gt;) :: f (a -&gt; b) -&gt; f a -&gt; f b 做到这一点,因为两个操作数的f 效果是独立的:第一个操作数必须产生一个纯函数,而f 效果在将该函数应用于参数之前已经发生
    • 接受了答案,因为它完全说服了我。但是关于flipping,我还没有完全得到你关于MonadApplicative 的最后评论。也许我得睡一会儿。
    • 在一些不寻常的情况下,mapM 可能比traverse 更有效。我在野外知道的一个是vector。 monadic 流式处理框架可以为mapM 执行traverse 不可用的魔术技巧。
    • @EnricoMariaDeAngelis:我用=&lt;&lt;而不是&gt;&gt;=来说明,类比函数应用:($)(&lt;*&gt;)(=&lt;&lt;)是各种应用。根据经验,f $ xfx 是纯的(尽管f 可能会返回有效的计算,如putStrLn $ "hello");在f &lt;*&gt; xfx中是有效果的,但是效果是独立的,只有函数的result可能依赖于参数的值;而在f =&lt;&lt; x 中,两者同样有效,但函数的效果 也可能取决于参数值。
    猜你喜欢
    • 2021-09-21
    • 2013-11-15
    • 2018-12-05
    • 2012-10-18
    • 2016-05-06
    • 1970-01-01
    • 2019-06-29
    • 1970-01-01
    • 2011-09-27
    相关资源
    最近更新 更多