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

mer*_*ict 43 monads haskell applicative fam-proposal

鉴于:

Applicative m, Monad m => mf :: m (a -> b), ma :: m a
Run Code Online (Sandbox Code Playgroud)

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

mf <*> ma === do { f <- mf; a <- ma; return (f a) }
Run Code Online (Sandbox Code Playgroud)

或者更简洁:

(<*>) === ap
Run Code Online (Sandbox Code Playgroud)

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

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

因此,有两个合法的,非平凡的推导<*>来源于>>=并且return具有不同的行为.在某些情况下,既没有这两个推导是可取的.

例如,(<*>) === ap法律强制Data.Validation定义两种不同的数据类型:ValidationAccValidation.前者有一个Monad类似于ExceptT的实例,以及一个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) }
Run Code Online (Sandbox Code Playgroud)

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

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

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

还有一个奖金问题:

  • 怎么做AlternativeMonadPlus适合这一切?

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

mer*_*ict 10

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


我认为只有一个合理的Functor例子来自Applicative:

fmap f fa = pure f <*> fa
Run Code Online (Sandbox Code Playgroud)

假设这是独一无二的,那么根据该法则Functor应该是超类Applicative.同样,我认为只有一个合理的Functor实例来自Monad:

fmap f fa = fa >>= return . f
Run Code Online (Sandbox Code Playgroud)

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

pigworker(原始Applicative论文的第一作者)写道:

"当然不会跟随.这是一个选择."

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

duplode同样写道:

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

"关于LeftA/ RightA想法:标准库中的其他地方也有类似的情况(例如SumProductin Data.Monoid).做同样的问题Applicative是功率 - 权重关系太低而无法证明额外的精度/灵活性.会使应用风格变得不那么令人愉快."

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


Ørj*_*sen 9

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

(>>=) fail (=<<) (>=>) (<=<) join foldM foldM_
Run Code Online (Sandbox Code Playgroud)

以下是该模块中的函数,其中一个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
Run Code Online (Sandbox Code Playgroud)

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

  • 当然它不遵循.这是一个选择.为什么你认为这是一个糟糕的选择?什么时候使应用符号的明显Moggi风格的翻译成为一种危险的误读? (8认同)
  • @mergeconflict一致性,可理解性,简单性和API简化。考虑到所有这些因素,在特殊情况下,除了明显的实例外,您还需要其他东西的额外类型是要付出很小的代价。给定一个设计良好的库,如果需要,在实例之间进行切换将很轻松-例如,您的示例包在`Validation`,`AccValidation`和`Either`之间提供了方便的同构。 (2认同)

dup*_*ode 6

在我自己(也许是错误的)直觉中,给定的pure f <*> ma <*> mb,不需要任何预定的排序,因为没有一个值相互依赖.

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

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

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

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

这听起来不太可能,因为它Applicative甚至不是Monad()的超类.然而,这些"法则"允许代码的读者进行转换,这同样重要.

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

  • @mergeconflict数学关系走向另一个方向:`return`和`ap`遵循应用定律,就像`\ fu - > pure f*u`和`\ fm - > m >> = return一样.f`遵循仿函数法则.Applicative-Monad提案反映了这些事实.现在,如果有多个实例可能只有其中一个是规范的,那么其他实例将被降级为`newtype`s.最简单的选择是选择匹配的实例集.它可以更容易推理代码,甚至可以免费提供实例(例如,从`Monad`开始,然后定义`(<*>)= ap`等). (2认同)