根据Monad在Applicative方面定义Functor是否更好,反之亦然?

时间:2013-10-28 12:50:09

标签: haskell monads functor applicative

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

假设您有一个T a类型,可以为其提供Monad个实例。由于每个monad都是Applicative,因为我分配pure = return(<*>) = ap,然后每个应用都是Functor来自fmap f x = pure f <*> x,定义您的实例是否更好?首先Monad,然后平凡地提供TApplicative的{​​{1}}个实例?

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

5 个答案:

答案 0 :(得分:25)

我倾向于编写并首先编写Functor实例。这是因为如果您使用LANGUAGE DeriveFunctor编译指示,那么data Foo a = Foo a deriving ( Functor )大部分时间都可以使用。

ApplicativeMonad更为通用时,棘手的问题就是实例协议。例如,这是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 - 到 - Monad顺序中的实例,并且单独考虑每个实例都是正确的。很遗憾,ApplicativeMonad个实例未对齐:ap(<*>)(>>)(*>)明显不同。

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

出于敏感目的,特别是一旦申请人/ Monad提案在每个人手中,这些都应该一致。如果您定义了instance Applicative (Err e) where { pure = return; (<*>) = ap },则他们对齐。

但是,最后,你可能有能力仔细挑逗ApplicativeMonad中的差异,以便它们以良性方式表现出不同的行为 - 例如拥有更加懒惰或更有效率{ {1}}实例。这实际上经常发生,我觉得陪审团仍然有点“良性”意味着什么,以及你的实例应该在什么样的“观察”。也许最合理的一些用途是在Facebook的Haxl项目中,Applicative实例Applicative实例更加并行化,因此效率更高一些相当严重的“未观察到的”副作用的代价。

在任何情况下,如果它们不同,请记录下来。

答案 1 :(得分:5)

与Abrahamson的答案相比,我经常选择反向方法。我手动定义Monad实例,并在Applicative中已定义的函数的帮助下定义FunctorControl.Monad,这使得这些实例相同绝对任何monad,即:

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

instance Functore SomeMonad where
  fmap = liftM

虽然这样FunctorApplicative的定义总是“无脑”并且很容易推理,但我必须指出,这不是最终的解决方案,因为有些情况,何时可以更有效地实施实例,甚至提供新功能。例如,ConcurrentlyApplicative实例同时执行...而Monad实例只能由于自然monad而按顺序执行它们。

答案 2 :(得分:3)

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

取决于ApplicativeMonadpurereturn通常同样容易,并且在扩展定义的哪个类中无关紧要。对于绑定,有时候走“分类方式”是合适的,即首先定义一个专门的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。在这种情况下,您应该明确定义<*>

答案 3 :(得分:0)

这里的神奇之处在于,Haskell使用了monad的Kleisli-tiplet符号 更方便的方法,如果有人想在工具等命令式编程中使用monad。

我问了同样的问题,如果你看到定义,答案会在一段时间后出现 在haskell中 Functor Applicative Monad 你错过了一个链接,这是monad的原始定义,其中只包含加入操作,可在HaskellWiki上找到。

从这个角度来看,你会看到haskell monad是如何构建的functor,applicative functors,monads和Kliesli triplet。

可在此处找到一个粗略的解释:https://github.com/andorp/pearls/blob/master/Monad.hs 其他人也有同样的想法:http://people.inf.elte.hu/pgj/haskell2/jegyzet/08/Monad.hs

答案 4 :(得分:0)

我认为你错误地理解子类在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)

是具有有效Metric实例和有效V函数的任何 Vector的有效norm实例。我们说规范会导致一个指标。请参阅http://en.wikipedia.org/wiki/Normed_vector_space#Topological_structure

Functor上的ApplicativeMonad超类与Metric类似,而不是Vectorreturn和{{1来自>>=的函数会导致MonadFunctor结构:

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

因此,用join aa = aa >>= id来定义FunctorApplicative操作在数学上是完全合理的。