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

bst*_*our 16 monads haskell functor applicative

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

假设您有一个T a可以给出实例的类型Monad.由于每个单子是Applicative通过分配pure = return(<*>) = ap,然后每一个应用性是Functor通过fmap f x = pure f <*> x,是它更好地定义你的情况Monad,然后再平凡给出T的实例ApplicativeFunctor

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

J. *_*son 25

我倾向于编写并Functor首先编写实例.这是因为如果你使用LANGUAGE DeriveFunctorpragma,那么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
Run Code Online (Sandbox Code Playgroud)

上面我按顺序定义了实例,并且Functor单独Monad考虑每个实例都是正确的.不幸的是,ApplicativeMonad实例并不一致:ap(<*>)是因为是观察地不同的(>>)(*>).

Err "hi" <*>  Err "bye" == Err "hibye"
Err "hi" `ap` Err "bye" == Err "hi"
Run Code Online (Sandbox Code Playgroud)

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

但是,最后,你可能能够仔细地区分差异Applicative并使Monad它们以良性的方式表现出不同的行为 - 比如拥有一个更懒惰或更有效的Applicative实例.这实际上经常发生,我觉得陪审团仍然对"良性"意味着什么以及你的实例应该在什么样的"观察"下进行一点点.也许最合理的一些用途是在Facebook的Haxl项目中,Applicative实例比实例更加并行化Monad,因此以一些相当严重的"未观察到的"副作用为代价更有效.

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


Nik*_*kov 5

与Abrahamson的回答相比,我经常选择反向方法.我手动定义了Monad实例,ApplicativeFunctor在其中使用已经定义的函数来定义和使用它Control.Monad,这使得这些实例对于绝对任何monad都是相同的,即:

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

instance Functore SomeMonad where
  fmap = liftM
Run Code Online (Sandbox Code Playgroud)

虽然这种方式定义Functor并且Applicative始终是"无脑"并且很容易推理,但我必须注意,这不是最终的解决方案,因为有些情况下,实例可以更有效地实现甚至提供新功能.例如,Applicative实例Concurrently执行的事......同时,而Monad实例只能按顺序执行这些由于自然单子.

  • @epsilonhalbe:那是无关紧要的; 子类化与实例定义之间的依赖关系无关. (2认同)
  • 唯一的问题是,有时它们的效率低于手写版本.列表monad是一个很好的例子,其中`liftM`的效率低于没有重写规则的手写`map`. (2认同)