簡體   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