繁体   English   中英

为什么Applicative应该是Monad的超类?

[英]Why should Applicative be a superclass of Monad?

鉴于:

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

它似乎被认为是一项法律:

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

或者更简洁:

(<*>) === ap

Control.Applicative文档<*>是“顺序应用程序”,这表明(<*>) = ap 这意味着<*>必须从左到右依次评估效果,以便与>>= ...保持一致但是这感觉不对。 McBride和Paterson的原始论文似乎暗示从左到右的排序是任意的:

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

因此,对于<*>两个合法的,非平凡的推导,其来自>>=return ,具有不同的行为。 在某些情况下, 无论这两个推导是可取的。

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

之前在StackOverflow上有过一些关于这个问题的讨论 ,但我认为它并没有真正解决问题:

为什么这应该是法律?

函子,应用程序和单子的其他定律 - 例如同一性,相关性等 - 表达了这些结构的一些基本的数学属性。 我们可以使用这些定律实现各种优化,并使用它们来证明我们自己的代码。 相比之下,我感觉像(<*>) === ap法则强加任意约束而没有相应的好处。

对于它的价值,我宁愿放弃法律支持这样的事情:

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

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

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

因此,从以下几个角度来处理问题:

  • 是否还有其他有关MonadApplicative法律?
  • 是否有任何固有的数学原因可以使用Applicative进行排序效果,就像它们对Monad
  • GHC或任何其他工具是否执行代码转换,假设/要求此法律为真?
  • 为什么Functor-Applicative-Monad提案被认为是一件非常好的事情? (引用将在这里非常感谢)。

还有一个奖金问题:

  • AlternativeMonadPlus如何适应这一切?

注意:主要编辑以澄清问题的内容。 @duplode发布的答案引用了早期版本。

好吧,我对目前给出的答案并不十分满意,但我认为附加的评论更具吸引力。 所以我在这里总结一下:


我认为Applicative只有一个合理的Functor实例:

fmap f fa = pure f <*> fa

假设这是独一无二的,那么Functor应该是Applicative的超类,并且具有该定律。 同样,我认为Monad只有一个明智的Functor实例:

fmap f fa = fa >>= return . f

再说一次, Functor应该是Monad的超类是有道理的。 我曾经(并且,实际上,仍有)的反对意见是, Monad有两个合理的Applicative实例,在某些特定情况下,甚至更合法; 为什么要一个?

pigworkerApplicative文件的第一作者)写道:

“当然不会跟随。这是一个选择。”

(在Twitter上 ):“在Monad中工作是一种不公正的惩罚;我们应该得到适用的注释”

duplode同样写道:

“...可以公平地说, pure === return(<*>) === ap不是强烈意义上的法律,例如monad法则是如此......”

“关于LeftA / RightA想法:标准库中的其他地方也存在类似的情况(例如, Data.Monoid SumProduct )。与Applicative一样的问题是权重关系太低而无法证明额外的精确性/灵活性。新类型会使应用风格的使用变得不那么令人愉快。“

所以,我很高兴看到这个选择明确说明,通过简单的推理证明它使最常见的情况更容易。

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

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

以下是该模块中的函数,其中Monad / MonadPlus约束可以轻易地告诉我们放宽到Applicative / 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

许多在后一组中确实ApplicativeAlternative版本,在任一Control.ApplicativeData.FoldableData.Traversable -但为什么需要学习摆在首位所有的重复?

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

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

Monad和Applicative有哪些法律(如果有的话)?

虽然可以公平地说pure === return(<*>) === ap (引用Control.Applicative )并不是强烈意义上的法律,例如monad法则是如此,它们有助于保持实例不足为奇。 鉴于每个Monad都会产生一个Applicative实例(实际上是两个实例,正如你所指出的那样), Applicative的实际实例与Monad给出的实际匹配是很自然的。 至于从左到右的惯例,遵循apliftM2的顺序 (当引入Applicative时已经存在,并且反映了由(>>=)强加的顺序)是一个明智的决定。 (注意,如果我们暂时忽略了多少(>>=)在实践中很重要,那么相反的选择也是可以防御的,因为它会使(<*>)(=<<)具有类似的类型,序列效果顺序相同。)

GHC或任何其他工具是否执行代码转换,假设/要求此法律为真?

鉴于Applicative甚至不是Monad的超类( 尚未 ),这听起来不太可能。 然而,这些“法则”允许代码的读者进行转换,这同样重要。

注意:如果你需要在Applicative实例中反转效果的顺序,就像Gabriel Gonzalez指出的那样,有Control.Applicative.Backwards 此外, (<**>)翻转参数但仍然从左到右排序效果,因此它也可用于反向排序。 类似地, (<*)不是flip (*>) ,因为两个序列效果都是从左到右。

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

sequenceA :: Applicative f, Traversable t => t (f a) -> f (t a)
join :: Monad m => m (m a) -> m a

什么是联接类型join . sequenceA join . sequenceA

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

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

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

所以,实际上,对每个与它兼容的Monad (在(<*>) = ap意义上)都有一个Applicative是一个非常好的主意。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM