为什么应用程序是Monad的超类?

鉴于:

Applicative m, Monad m => mf :: m (a -> b), ma :: ma 

这似乎被认为是一个法律:

 mf <*> ma === do { f <- mf; a <- ma; return (fa) } 

或者更简洁:

 (<*>) === ap 

Control.Applicative的文档说<*>是“顺序应用程序”,这表明(<*>) = ap 。 这意味着, <*>必须按顺序从左到右评估效果,与>>= …保持一致。但这种感觉是错误的。 McBride和Paterson的原始文件似乎意味着从左到右的sorting是任意的:

IO monad,实际上任何Monad都可以通过使用pure = return<*> = ap我们也可以用相反顺序执行计算的ap的变体 ,但是在本文中我们应该保持从左到右的顺序。

所以有两个合法的,不重要的派生出现在<*> >>=return ,具有不同的行为。 而且在某些情况下,这两个派生都不可取。

例如, (<*>) === ap法则强制Data.Validation定义两个不同的数据types: ValidationAccValidation 。 前者具有类似于ExceptT的Monad实例,以及由于在第一个错误之后停止而具有有限效用的一致Applicative实例。 后者另一方面并没有定义Monad实例,因此可以自由地实现一个更有用的Applicative来积累错误。

先前在StackOverflow上已经讨论过这个问题,但是我不认为它真的有问题:

为什么这应该是一个法律?

函子,应用程序和monad的其他法则(如身份,关联等)表示这些结构的一些基本的math属性。 我们可以使用这些法则来实现各种优化,并使用它们来certificate我们自己的代码。 相反,我感觉像(<*>) === ap法则强加一个任意的约束,没有相应的好处。

为了什么是值得的,我宁愿抛弃法律来支持这样的事情:

 newtype LeftA ma = LeftA (ma) instance Monad m => Applicative (LeftA m) where pure = return mf <*> ma = do { f <- mf; a <- ma; return (fa) } newtype RightA ma = RightA (ma) instance Monad m => Applicative (RightA m) where pure = return mf <*> ma = do { a <- ma; f <- mf; return (fa) } 

我认为这正确地捕捉了两者之间的关系,而没有不适当的约束。

所以,从以下几个angular度来处理这个问题:

  • 还有其他法律有关MonadApplicative
  • 是否有任何内在的math原因为Applicativesorting方式与Monad
  • GHC或任何其他工具是否执行代码转换,假定/要求这个法则是正确的?
  • 为什么Functor-Applicative-Monad的提议认为这是一件非常好的事情? (引用在这里非常感谢)。

还有一个额外的问题:

  • AlternativeMonadPlus如何适应这一切?

注意:主要编辑澄清问题的肉。 回复@duplode引用了早期版本。

那么到目前为止,我对这个问题的答案并不是非常满意,但是我认为这些问题的意见更加引人注目。 所以我在这里总结一下:


我认为只有一个明显的Functor实例来自Applicative

 fmap f fa = pure f <*> fa 

假设它是独一无二的,那Functor应该成为Applicative的超类,这是有道理的。 同样,我认为Monad只有一个明显的Functor实例:

 fmap f fa = fa >>= return . f 

所以再说一次, Functor应该是Monad的超类。 我曾经(而且实际上还有)的反对意见是, Monad有两个明显的Applicative实例,在某些特定情况下,还有更多的合法实例; 那么为什么要求呢?

pigworker ( 原Applicative文件中的第一作者)写道:

“当然不会,这是一个select。”

(在微博上 ):“记事是对单身工作的不公正惩罚,我们应该得到应用记法”

duplode同样写道:

“……可以肯定地说, pure === return(<*>) === ap在强烈的意义上并不是法律,例如monad法则是如此……”

“在LeftA / RightA思想中:在标准库的其他地方有类似的情况(例如, Data.Monoid SumProduct )。与Applicative相同的问题是,权重关系太低而不能certificate额外的精度/灵活性,新型将使应用风格使用起来不那么愉快。“

所以,我很高兴地看到这个select是明确陈述的,通过简单的推理certificate它使最常见的情况更容易。

除此之外,你问为什么Functor-Applicative-Monadbuild议是好事。 原因之一是缺乏统一意味着API有很多重复。 考虑标准的Control.Monad模块。 以下是该模块中基本上使用MonadMonadPlus没有)限制的function:

 (>>=) fail (=<<) (>=>) (<=<) join foldM foldM_ 

以下是该模块中Monad / MonadPlus约束可以轻松应用于MonadPlus / Alternative

 (>>) return mzero mplus mapM mapM_ forM forM_ sequence sequence_ forever msum filterM mapAndUnzipM zipWithM zipWithM_ replicateM replicateM_ guard when unless liftM liftM2 liftM3 liftM4 liftM5 ap 

后者中的许多组在Control.ApplicativeData.FoldableData.Traversable 具有ApplicativeAlternative版本,但是为什么首先需要了解所有的重复?

并且在我自己的(也许是错误的)直觉中,给定pure f <*> ma <*> mb ,不需要任何预定的测序,因为这些值都不相互依赖。

值不,但效果。 (<*>) :: t (a -> b) -> ta -> tb意味着您必须以某种方式组合参数的效果才能获得整体效果。 组合是否可交换取决于实例是如何定义的。 例如, Maybe的实例是可交换的,而列表的默认“交叉连接”实例不是。 因此,有些情况下你不能避免强加一些命令。

Monad和Applicative有什么法律?

虽然pure === return(<*>) === ap (引用Control.Applicative )在公平的意义上并不是强制意义上的法律,例如monad法则是这样,它们有助于保持实例不令人吃惊。 鉴于每个Monad引起了Applicative一个实例(实际上是两个实例,正如你指出的那样),所以Applicative的实际实例与Monad给我们的实例是自然匹配的。 至于从左到右的约定,遵循apliftM2的顺序 (当Applicative被引入时已经存在,并且反映(>>=)强加的顺序)是一个明智的决定。 (注意,如果我们在实践中忽略了多less(>>=)事情,相反的select也是可以辩护的,因为它会使(<*>)(=<<)具有类似的types,按相同的顺序排列效果。)

GHC或任何其他工具是否执行代码转换,假定/要求这个法则是正确的?

这听起来不太可能,因为Applicative甚至不是Monad的超类。 然而,这些“法律”允许守则的读者进行同样重要的转换。

注意:如果您需要在Applicative实例中反转效果sorting,那么就有Control.Applicative.Backwards ,就像Gabriel Gonzalez指出的那样。 另外, (<**>)翻转参数,但是仍然从左向右排列效果,所以它也可以用来反转sorting。 同样, (<*)不是flip (*>) ,因为这两个序列效果从左到右。

只是为了logging,标题中的问题的答案是:考虑

 sequenceA :: Applicative f, Traversable t => t (fa) -> f (ta) join :: Monad m => m (ma) -> ma 

什么是join . sequenceA的typesjoin . sequenceA join . sequenceA

  1. ATP: Monad m, Traversable m => m (ma) -> ma
  2. 现状: Applicative m, Monad m, Traversable m => m (ma) -> ma

授予, join . sequenceA join . sequenceA是一个人为的情况,但是肯定有一些情况下你需要一个monad,但是你也想使用Applicative操作<*>*><*<**>等等。然后:

  • 有两个单独的约束来捕获这两个操作是令人讨厌的。
  • Applicative名称是(恕我直言)比传统的monad操作更好。
  • 有两个不同的名字,例如ap>><<等,是讨厌的(“哦,你不能在那里使用,这是Monad不是一个Applicative ”;“哦,你必须使用<*>那里,这是一个Applicative不是一个Monad “)。
  • 在真正的monad中,顺序是非常重要的,这意味着如果>>*>做不同的事情,那么你实际上不能使用Applicative语法,因为它会做你不期望的事情。

因此,务实地,对每一个与之兼容的Monad (在(<*>) = ap意义上)都是一个真正的好主意。