18

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

假设你有一个类型T a可以被赋予一个Monad. 由于每个 monad 都是一个Applicativeby 分配pure = returnand (<*>) = ap,然后每个 applicative 都是一个Functorvia fmap f x = pure f <*> x,所以最好先定义你的实例Monad,然后再简单地给出andT的实例?ApplicativeFunctor

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

4

5 回答 5

25

我倾向于先写和看写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单独来看Monad,每个实例都是正确的。不幸的是,ApplicativeandMonad实例不对齐:ap并且与is和(<*>)明显不同。(>>)(*>)

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

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

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

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

于 2013-10-28T13:03:01.577 回答
6

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

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

instance Functore SomeMonad where
  fmap = liftM

虽然这种方式和的定义Functor总是Applicative“无脑”并且很容易推理,但我必须注意这不是最终的解决方案,因为在某些情况下,实例可以更有效地实现甚至提供新功能. 例如,执行事物的Applicative实例......并发,而由于自然单子,实例只能顺序执行它们。ConcurrentlyMonad

于 2013-10-28T19:54:20.297 回答
3

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

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

其他时候,Applicative实例可以很容易地编写,或者比通用的 Monad 派生实现更有效。在这种情况下,您绝对应该<*>单独定义。

于 2013-10-28T13:01:17.543 回答
0

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

我问了同样的问题,过了一会儿就有答案了,如果你在 haskell 中看到FunctorApplicativeMonad的定义,你会错过一个链接,这是 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

于 2013-11-17T00:09:52.067 回答
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出现是因为范数空间的概念真的很弱,除非你还假设一个向量空间结构;第二个出现是因为(给定向量空间) aNorm诱导 a Metric,您可以通过观察来证明

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

任何具有有效Metric实例和有效函数的有效实例。我们说规范引发了一个度量。请参阅http://en.wikipedia.org/wiki/Normed_vector_space#Topological_structure VVectornorm

Functor和超ApplicativeMonad是 like Metric, not like Vector:induced和结构中的returnand>>=函数:MonadFunctorApplicative

  • fmap: 可以定义为fmap f a = a >>= return . fliftM在 Haskell 98 标准库中。
  • purereturn: 与;的操作相同 这两个名字是从Applicative不是Monad.
  • <*>: 可以定义为af <*> ax = af >>= \ f -> ax >>= \ x -> return (f x)liftM2 ($)在 Haskell 98 标准库中。
  • join: 可以定义为join aa = aa >>= id

因此,在数学上,用 来定义FunctorApplicative操作是完全明智的Monad

于 2015-04-15T16:30:11.327 回答