【问题标题】:Is it better to define Functor in terms of Applicative in terms of Monad, or vice versa?用 Monad 的 Applicative 来定义 Functor 更好,反之亦然?
【发布时间】:2013-11-07 06:04:19
【问题描述】:

这是一个普遍的问题,与任何一段代码无关。

假设您有一个类型T a,可以给它一个Monad 的实例。由于通过分配pure = return(<*>) = ap,每个monad 都是Applicative,然后每个应用程序都是Functor 通过fmap f x = pure f <*> x,所以最好先定义Monad 的实例,然后再简单地给出@ ApplicativeFunctor 的 987654329@ 实例?

对我来说感觉有点落后。如果我在做数学而不是编程,我会认为我会首先证明我的对象是一个仿函数,然后继续添加限制,直到我也证明它是一个 monad。我知道 Haskell 只是受到范畴论的启发,显然构建证明时使用的技术不是编写有用程序时使用的技术,但我想从 Haskell 社区获得意见。从Monad 降到Functor 会更好吗?或从FunctorMonad

【问题讨论】:

    标签: haskell monads functor applicative


    【解决方案1】:

    Functor 实例通常很容易定义,我通常会手动完成。

    对于ApplicativeMonad,视情况而定。 purereturn 通常同样容易,并且将扩展定义放在哪个类中并不重要。对于绑定,有时采用“类别方式”是有益的,即先定义一个专门的join' :: (M (M x)) -> M x,然后再定义a>>=b = join' $ fmap b a(如果您根据>>= 定义了fmap,这当然行不通) .然后将(>>=) 重新用于Applicative 实例可能很有用。

    其他时候,Applicative 实例可以很容易地编写or is more efficient than the generic Monad-derived implementation。在这种情况下,您绝对应该单独定义<*>

    【讨论】:

      【解决方案2】:

      我倾向于先写Functor 实例。加倍如此,因为如果您使用 LANGUAGE DeriveFunctor 杂注,那么 data Foo a = Foo a deriving ( Functor ) 大部分时间都有效。

      当您的Applicative 可能比您的Monad 更通用时,棘手的地方在于实例的一致性。例如,这是一个Err 数据类型

      data Err e a = Err [e] | Ok a deriving ( Functor )
      
      instance Applicative (Err e) where
        pure = Ok
        Err es <*> Err es' = Err (es ++ es')
        Err es <*> _       = Err es
        _      <*> Err es  = Err es
        Ok  f  <*> Ok  x   = Ok (f x)
      
      instance Monad (Err e) where
        return = pure
        Err es >>= _ = Err es
        Ok  a  >>= f = f a
      

      上面我以Functor-to-Monad 的顺序定义了实例,并且单独来看,每个实例都是正确的。不幸的是,ApplicativeMonad 实例没有对齐:ap(&lt;*&gt;) 明显不同,(&gt;&gt;)(*&gt;) 也是如此。

      Err "hi" <*>  Err "bye" == Err "hibye"
      Err "hi" `ap` Err "bye" == Err "hi"
      

      出于敏感性目的,特别是一旦 Applicative/Monad 提案在每个人的手中,这些都应该保持一致。如果您定义了instance Applicative (Err e) where { pure = return; (&lt;*&gt;) = ap },那么它们对齐。

      但是,最后,您可能能够仔细梳理ApplicativeMonad 中的差异,以便它们以良性方式表现不同——例如拥有一个更懒惰或更高效的Applicative 实例。这实际上发生得相当频繁,我觉得陪审团仍然对“良性”的含义以及您的实例应该在何种“观察”下保持一致。在 Facebook 的 Haxl 项目中,可能最常用的就是 Applicative 实例比Monad 实例更并行,因此效率更高相当严重的“未观察到”副作用。

      无论如何,如果它们不同,请记录下来。

      【讨论】:

        【解决方案3】:

        与亚伯拉罕森的回答相比,我经常选择一种相反的方法。我手动定义了Monad 实例,并在Control.Monad 中已经定义的函数的帮助下定义了ApplicativeFunctor,这使得这些实例对于绝对任何monad 都是相同的,即:

        instance Applicative SomeMonad where
          pure = return
          (<*>) = ap
        
        instance Functore SomeMonad where
          fmap = liftM
        

        虽然 FunctorApplicative 的定义总是“无脑”并且很容易推理,但我必须注意这不是最终的解决方案,因为在某些情况下,当实例可以更有效地实施,甚至提供新功能。例如,ConcurrentlyApplicative 实例可以同时执行……而 Monad 实例由于自然单子的原因只能按顺序执行。

        【讨论】:

        • 明确地说,我并不是说这是一个坏方法。我只是强调F-&gt;A-&gt;M 方法的优点和缺点。我个人倾向于按顺序定义最容易正确使用的功能。
        • 我认为 E.Kmett 在 haskellcast 中说过,Monad 将成为Applicative 的子类,但我不知道具体何时会发生这种变化。
        • @epsilonhalbe:这无关紧要;子类化与实例定义之间的依赖无关。
        • 唯一的问题是,有时这些比手写版本效率低。 list monad 就是一个很好的例子,其中liftM 的效率低于没有重写规则的手写map
        【解决方案4】:

        这里的神奇之处在于,Haskell 使用了单子的 Kleisli-tiplet 表示法,即 如果有人想在像工具这样的命令式编程中使用 monad,这是一种更方便的方法。

        我问了同样的问题,如果你看到定义,过一会儿就会得到答案 FunctorApplicativeMonad在haskell中你漏掉了一个链接,这是monad的原始定义,它只包含join 操作,可以在HaskellWiki 上找到。

        通过这个观点,您将看到 haskell monad 是如何构建 functor、applicative functor、monad 和 Kliesli 三元组的。

        可以在这里找到粗略的解释:https://github.com/andorp/pearls/blob/master/Monad.hs 和其他有相同想法的人在这里:http://people.inf.elte.hu/pgj/haskell2/jegyzet/08/Monad.hs

        【讨论】:

          【解决方案5】:

          我认为您误解了 Haskell 中子类的工作方式。它们不像 OO 子类!相反,一个子类约束,比如

          class Applicative m => Monad m
          

          说“任何具有规范 Monad 结构的类型也必须具有规范 Applicative 结构”。放置这样的约束有两个基本原因:

          • 子类结构引入超类结构。
          • 超类结构是子类结构的自然子集。

          例如,考虑:

          class Vector v where
              (.^) :: Double -> v -> v
              (+^) :: v -> v -> v
              negateV :: v -> v
          
          class Metric a where
              distance :: a -> a -> Double
          
          class (Vector v, Metric v) => Norm v where
              norm :: v -> Double
          

          Norm 上的第一个超类约束的出现是因为范数空间的概念真的很弱,除非你还假设一个向量空间结构;第二个出现是因为(给定一个向量空间)Norm 引发了Metric,你可以通过观察来证明这一点

          instance Metric V where
              distance v0 v1 = norm (v0 .^ negateV v1)
          

          any V 的有效 Metric 实例,具有有效的 Vector 实例和有效的 norm 函数。我们说规范引入一个度量。见http://en.wikipedia.org/wiki/Normed_vector_space#Topological_structure

          Monad 上的 FunctorApplicative 超类类似于 Metric,而不是类似于 Vectorreturn&gt;&gt;= 函数来自 Monadfmap f a = a &gt;&gt;= return . f 和 @9876 @结构:

          • fmap:可以定义为fmap f a = a &gt;&gt;= return . f,在Haskell 98标准库中是liftM
          • pure:与return的操作相同;这两个名字是 Applicative 不是 Monad 的超类时的遗产。
          • &lt;*&gt;:可以定义为af &lt;*&gt; ax = af &gt;&gt;= \ f -&gt; ax &gt;&gt;= \ x -&gt; return (f x),在Haskell 98标准库中是liftM2 ($)
          • join:可以定义为join aa = aa &gt;&gt;= id

          因此,在数学上,根据Monad 定义FunctorApplicative 操作是完全明智的。

          【讨论】:

            猜你喜欢
            • 2011-11-05
            • 2012-01-29
            • 1970-01-01
            • 2019-06-30
            • 2012-11-12
            • 2015-07-02
            • 1970-01-01
            相关资源
            最近更新 更多