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

时间:2014-06-09 02:02:48

标签: haskell monads applicative fam-proposal

假设:

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

似乎被视为一项法律:

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

或更简洁:

(<*>) === ap

documentation for Control.Applicative表示<*>是“顺序申请”,这表明(<*>) = ap。这意味着<*>必须从左到右依次评估效果,以便与>>=保持一致......但这感觉不对。 McBride and Paterson's original paper似乎意味着从左到右的排序是任意的:

  

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

因此,来自<*>>>=的{​​{1}}有两个合法,非平凡的派生,具有不同的行为。在某些情况下,这两个派生中的都不合适。

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

StackOverflow上有一些discussion about this previously,但我认为它并没有真正解决问题:

为什么这是一项法律?

有关仿函数,应用程序和monad的其他定律 - 例如同一性,相关性等 - 表达了这些结构的一些基本的数学属性。我们可以使用这些定律实现各种优化,并使用它们来证明我们自己的代码。相比之下,我觉得Applicative法则强加了一种没有相应利益的任意约束。

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

(<*>) === 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) } Monad相关的其他法律?
  • 对于Applicative的排序效果是否有任何固有的数学原因,与Applicative的方式相同?
  • GHC或任何其他工具是否执行假设/要求此法律为真的代码转换?
  • 为什么Functor-Applicative-Monad提案被认为是一件非常好的事情? (引用将在这里非常感激。)

还有一个奖金问题:

  • MonadAlternative如何适应所有这些?

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

4 个答案:

答案 0 :(得分:9)

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


我认为Functor之后只有一个明智的Applicative实例:

fmap f fa = pure f <*> fa

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

fmap f fa = fa >>= return . f

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

pigworkeroriginal Applicative paper上的第一作者)写道:

  

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

     

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

duplode同样写道:

  

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

     

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

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

答案 1 :(得分:7)

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

(>>=) 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

后一组 中的许多人都有ApplicativeAlternativeControl.Applicative中的Data.FoldableData.Traversable个版本 - 但为什么需要首先学习所有重复?

答案 2 :(得分:5)

  

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

价值观并非如此,但效果确实如此。 (<*>) :: t (a -> b) -> t a -> t b意味着您必须以某种方式组合参数的效果才能获得整体效果。组合是否可交换取决于实例的定义方式。例如,Maybe的实例是可交换的,而默认情况下,&#34;交叉连接&#34;列表的实例不是。因此,在某些情况下,您无法强制执行某些订单。

  

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

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

  

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

由于Applicative甚至不是Monadyet)的超类,这听起来不太可能。然而,这些法律和#34;允许代码的读者进行转换,这同样重要。

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

答案 3 :(得分:1)

仅供记录,标题中问题的答案是:考虑

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

join . sequenceA的类型是什么?

  1. ATP:Monad m, Traversable m => m (m a) -> m a
  2. 现状:Applicative m, Monad m, Traversable m => m (m a) -> m a
  3. 当然,join . sequenceA是一种人为的情况,但肯定会出现需要 monad的情况,但您也希望使用Applicative操作<*>*><*<**>等。然后:

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

    所以,实际上,每Applicative Monad(<*>) = ap兼容(在<?xml version="1.0" encoding="utf-8"?> <Report xmlns="FPPD2.srdl" baseAmount_baseCreditAmount_TOTAL="1032.14" baseAmount_baseCreditAmount_COUNT="3" Today="2015-04-14T02:16:44" DocumentNumber=" 41" ExecDate="2015-04-14"> <Detail_2_1> <Detail_2_1_Group_Collection> <Item RowNumber="1" transactionDate="2004-04-01" accountCode_label_2="Account Code:" accountCode_3="PK181010" description_1="Telephone" paymentAccount_1="BANK" transactionReference="404TELE001NYC" description_label_2="Description:" description_2="Teleford communications" baseAmount_baseCreditAmount="428.57" baseAmount_baseDebitAmount_x="0.00" baseAmount_amount_1="428.57" baseAmount_baseDebitAmount="" baseAmount_amount_10="-428.57" supplierName_label_1="Supplier Name:" supplierName_1="Teleford &amp; Communications" addressLine1_label_1="Address Line 1:" addressLine1_1="57 Harpermoorish Ave" addressLine2_label_1="Address Line 2:" addressLine2_1="" addressLine3_label_1="Address Line 3:" addressLine3_1="" addressLine4_label_1="Address Line 4:" addressLine4_1="Birmingham RG5 5BJ" addressLine5_label_1="Address Line 5:" addressLine5_1="UK" TownCity_label_1="Town/City:" TownCity_1="Birmingham" State_label_1="State:" State_1="" StateCode_label_1="State Code:" StateCode_1="" PostalCode_label_1="Postal Code:" PostalCode_1="RG5 5BJ" Country_label_1="Country:" Country_1="UK" document_1="Document:" TextBox_15="404TELE001NYC" bankAccountName_label_1="Bank Account Name:" bankAccountName_1="Travelbug Software Ltd" bankAccountNumber_label_1="Bank Account Number:" bankAccountNumber_1="DE21500500001234567897" BankSortCode_label_1="Bank Sort Code:" BankSortCode_1="BUINBGSF123" BankSubcode_label_1="Bank Subcode:" BankSubcode_1="" BankBranch_label_1="Bank Branch:" BankBranch_1="Southampton" BankDetailsCode_label_1="Bank Details Code:" BankDetailsCode_1="81010" BankName_label_1="Bank Name:" BankName_1="Midland Bank Plc" SwiftCode_label_1="Extension Swift Code:" SwiftCode_1="" /> <Item RowNumber="2" transactionDate="2004-05-01" accountCode_label_2="Account Code:" accountCode_3="PK181010" description_1="Telephone" paymentAccount_1="BANK" transactionReference="405TELE001NYC" description_label_2="Description:" description_2="Teleford communications" baseAmount_baseCreditAmount="428.57" baseAmount_baseDebitAmount_x="0.00" baseAmount_amount_1="857.14" baseAmount_baseDebitAmount="" baseAmount_amount_10="-857.14" supplierName_label_1="Supplier Name:" supplierName_1="Teleford &amp; Communications" addressLine1_label_1="Address Line 1:" addressLine1_1="57 Harpermoorish Ave" addressLine2_label_1="Address Line 2:" addressLine2_1="" addressLine3_label_1="Address Line 3:" addressLine3_1="" addressLine4_label_1="Address Line 4:" addressLine4_1="Birmingham RG5 5BJ" addressLine5_label_1="Address Line 5:" addressLine5_1="UK" TownCity_label_1="Town/City:" TownCity_1="Birmingham" State_label_1="State:" State_1="" StateCode_label_1="State Code:" StateCode_1="" PostalCode_label_1="Postal Code:" PostalCode_1="RG5 5BJ" Country_label_1="Country:" Country_1="UK" document_1="Document:" TextBox_15="405TELE001NYC" bankAccountName_label_1="Bank Account Name:" bankAccountName_1="Travelbug Software Ltd" bankAccountNumber_label_1="Bank Account Number:" bankAccountNumber_1="DE21500500001234567897" BankSortCode_label_1="Bank Sort Code:" BankSortCode_1="BUINBGSF123" BankSubcode_label_1="Bank Subcode:" BankSubcode_1="" BankBranch_label_1="Bank Branch:" BankBranch_1="Southampton" BankDetailsCode_label_1="Bank Details Code:" BankDetailsCode_1="81010" BankName_label_1="Bank Name:" BankName_1="Midland Bank Plc" SwiftCode_label_1="Extension Swift Code:" SwiftCode_1="" /> <Item RowNumber="3" transactionDate="2004-02-01" accountCode_label_2="Account Code:" accountCode_3="PK181015" description_1="Leased Lines" paymentAccount_1="BANK" transactionReference="402TELE003TOK" description_label_2="Description:" description_2="Integration International" baseAmount_baseCreditAmount="175.00" baseAmount_baseDebitAmount_x="0.00" baseAmount_amount_1="1,032.14" baseAmount_baseDebitAmount="" baseAmount_amount_10="-1,032.14" supplierName_label_1="Supplier Name:" supplierName_1="Integration International" addressLine1_label_1="Address Line 1:" addressLine1_1="49, Station Road" addressLine2_label_1="Address Line 2:" addressLine2_1="" addressLine3_label_1="Address Line 3:" addressLine3_1="" addressLine4_label_1="Address Line 4:" addressLine4_1="London E4 7BJ" addressLine5_label_1="Address Line 5:" addressLine5_1="" TownCity_label_1="Town/City:" TownCity_1="London" State_label_1="State:" State_1="" StateCode_label_1="State Code:" StateCode_1="" PostalCode_label_1="Postal Code:" PostalCode_1="E4 7BJ" Country_label_1="Country:" Country_1="" document_1="Document:" TextBox_15="402TELE003TOK" bankAccountName_label_1="Bank Account Name:" bankAccountName_1="Creditor Account" bankAccountNumber_label_1="Bank Account Number:" bankAccountNumber_1="DE21500500009876543210" BankSortCode_label_1="Bank Sort Code:" BankSortCode_1="CRBABGSF" BankSubcode_label_1="Bank Subcode:" BankSubcode_1="" BankBranch_label_1="Bank Branch:" BankBranch_1="Cambridge" BankDetailsCode_label_1="Bank Details Code:" BankDetailsCode_1="81015" BankName_label_1="Bank Name:" BankName_1="Lloyds Bank" SwiftCode_label_1="Extension Swift Code:" SwiftCode_1="" /> </Detail_2_1_Group_Collection> </Detail_2_1> </Report> 意义上)是一个非常非常好的主意。