誰首先說了如下幾點? html
monad只是endofunctors類別中的幺半羣,問題是什麼? 程序員
在一個不過重要的注意事項上,這是真的,若是是這樣,你能給出一個解釋(但願有一個能夠被沒有Haskell經驗的人理解的解釋)嗎? 編程
首先,咱們將使用的擴展和庫: 數組
{-# LANGUAGE RankNTypes, TypeOperators #-} import Control.Monad (join)
其中, RankNTypes
是惟一對下面絕對必要的。 我曾經寫過一些有些人彷佛認爲有用的RankNTypes
的解釋 ,因此我會參考。 編程語言
引用Tom Crockett的優秀答案 ,咱們有: ide
monad是......
- 一個endofunctor, T:X - > X.
- 天然變換, μ:T×T - > T ,其中×表示仿函數組成
- 一個天然變換, η:I - > T ,其中我是X上的標識endofunctor
......知足這些法律:
- μ(μ(T×T)×T))=μ(T×μ(T×T))
- μ(η(T))= T =μ(T(η))
咱們如何將其轉換爲Haskell代碼? 那麼,讓咱們從天然轉型的概念開始: 函數
-- | A natural transformations between two 'Functor' instances. Law: -- -- > fmap f . eta g == eta g . fmap f -- -- Neat fact: the type system actually guarantees this law. -- newtype f :-> g = Natural { eta :: forall x. f x -> g x }
A型的形式的f :-> g
相似於一個功能類型,但代替它思想爲兩種類型 (的種類之間的函數 *
),把它做爲兩個函子 (各類之間的態射 * -> *
)。 例子: this
listToMaybe :: [] :-> Maybe listToMaybe = Natural go where go [] = Nothing go (x:_) = Just x maybeToList :: Maybe :-> [] maybeToList = Natural go where go Nothing = [] go (Just x) = [x] reverse' :: [] :-> [] reverse' = Natural reverse
基本上,在Haskell中,天然變換是從某種類型fx
到另外一種類型gx
的x
,使得x
類型變量對於調用者是「不可訪問的」。 所以,例如, sort :: Ord a => [a] -> [a]
不能成爲天然變換,由於它「挑剔」咱們可能爲a
實例化哪些類型。 我常用的一種直觀方式是: google
如今,讓咱們解決這個定義的條款。 spa
第一個子句是「endofunctor, T:X - > X」 。 好吧,Haskell中的每一個Functor
都是人們稱之爲「Hask類別」的endofunctor,其對象是Haskell類型(類型*
),其狀態是Haskell函數。 這聽起來像一個複雜的陳述,但它其實是一個很是微不足道的陳述。 它的意思是,一個Functor f :: * -> *
爲您提供了構建型的手段fa :: *
對於任何a :: *
和功能fmap f :: fa -> fb
出任何的f :: a -> b
,而且這些遵照了算子法則。
第二個子句:Haskell中的Identity
functor(它隨Platform一塊兒提供,因此你只須要導入它)就是這樣定義的:
newtype Identity a = Identity { runIdentity :: a } instance Functor Identity where fmap f (Identity a) = Identity (f a)
因此Tom Crockett定義的天然變換η:I - > T能夠用這種方式寫成任何Monad
實例t
:
return' :: Monad t => Identity :-> t return' = Natural (return . runIdentity)
第三個條款:Haskell中兩個仿函數的組合能夠經過這種方式定義(它也隨平臺一塊兒提供):
newtype Compose f g a = Compose { getCompose :: f (g a) } -- | The composition of two 'Functor's is also a 'Functor'. instance (Functor f, Functor g) => Functor (Compose f g) where fmap f (Compose fga) = Compose (fmap (fmap f) fga)
所以Tom Crockett定義的天然變換μ:T×T - > T能夠這樣寫:
join' :: Monad t => Compose t t :-> t join' = Natural (join . getCompose)
這是endofunctors類別中的monoid的聲明而後意味着Compose
(僅部分應用於其前兩個參數)是關聯的,而且Identity
是其標識元素。 即,如下同構持有:
Compose f (Compose gh) ~= Compose (Compose fg) h
Compose f Identity ~= f
Compose Identity g ~= g
這些很容易證實,由於Compose
和Identity
都被定義爲newtype
,而Haskell Reports將newtype
的語義定義爲被定義的類型和newtype
的數據構造函數的參數類型之間的同構。 例如,讓咱們證實Compose f Identity ~= f
:
Compose f Identity a ~= f (Identity a) -- newtype Compose f g a = Compose (f (g a)) ~= f a -- newtype Identity a = Identity a Q.E.D.
注意:不,這不是真的。 在某些時候,Dan Piponi本身對這個答案發表了評論說,這裏的因果偏偏相反,他在迴應James Iry的諷刺時寫了他的文章。 但它彷佛已被刪除,多是經過一些強迫性的整潔。
如下是我原來的答案。
Iry極可能已經讀過Monoids到Monads ,其中Dan Piponi(sigfpe)從Haskell的monoids中衍生monad,並對類別理論進行了大量討論並明確提到了「Hask上的endofunctors類別」。 不管如何,任何想知道monad在endofunctor類別中是monoid意味着什麼的人均可能從閱讀這個推導中受益。
詹姆斯·伊裏(James Iry)從他極具娛樂性的簡短,不完整和錯誤的編程語言歷史中獲得了這一特殊的措辭,其中他將其虛構地歸功於菲利普·瓦德勒(Philip Wadler)。
最初的引用來自Saunders Mac Lane 的工做數學家類別,這是類別理論的基礎文本之一。 在這裏 ,它多是瞭解其含義的最佳位置。
可是,我會採起刺。 原句是這樣的:
總而言之,X中的monad只是X的endofunctor類別中的monoid,產品×由endofunctors的組合和身份endofunctor設置的單位替換。
這裏的X是一個類別。 Endofunctors是從類別到自身的仿函數(就函數式程序員而言,它一般都是 Functor
,由於它們主要只處理一個類別;類型類別 - 但我離題了)。 但你能夠想象另外一個類別是「 X上的 endofunctors」類別。 這是一個類別,其中對象是endofunctors,而態射是天然變換。
在那些終結者中,其中一些多是單子。 哪些是monad? 正是那些在特定意義上是幺半羣的。 而不是拼寫出從monad到monoids的確切映射(由於Mac Lane確實比我但願的要好得多),我只是將它們各自的定義並排放在一塊兒讓你比較:
* -> *
帶有Functor
實例) join
在Haskell) return
) 稍微眯着眼睛,你可能會發現這兩個定義都是同一個抽象概念的實例。
我經過更好地理解Mac Lane的工做數學家類別理論中臭名昭着的引用來推斷這篇文章。
在描述某些東西時,描述它不是什麼一般一樣有用。
Mac Lane使用描述來描述Monad這一事實可能意味着它描述了monad特有的東西。 忍受我。 爲了更普遍地理解這個陳述,我認爲須要明確的是他並沒有描述monad獨有的東西; 該聲明一樣描述了Applicative和Arrows等。 出於一樣的緣由,咱們能夠在Int(Sum和Product)上有兩個monoid,咱們能夠在endofunctors類別中的X上有幾個monoid。 可是類似之處還有更多。
Monad和Applicative均符合標準:
(例如,在平常Tree a -> List b
,但在類別Tree -> List
)
Tree -> List
,只有List -> List
。 該語句使用「Category of ...」這定義了語句的範圍。 做爲示例,Functor類別描述了f * -> g *
的範圍,即, Any functor -> Any functor
,例如Tree * -> List *
或Tree * -> Tree *
。
分類聲明未指定的內容描述了容許任何內容和全部內容的位置 。
在這種狀況下,在仿函數中, * -> *
aka a -> b
未指定,這意味着Anything -> Anything including Anything else
。 當個人想象力跳轉到Int - > String時,它還包括Integer -> Maybe Int
,或甚至Maybe Double -> Either String Int
,其中a :: Maybe Double; b :: Either String Int
a :: Maybe Double; b :: Either String Int
。
因此聲明以下:
:: fa -> gb
(即任何參數化類型的參數化類型) :: fa -> fb
(即任何一個參數化類型爲相同的參數化類型)...換句話說, 那麼,這個結構的力量在哪裏? 爲了欣賞完整的動態,我須要看到一個monoid的典型圖形(單個對象看起來像一個身份箭頭, :: single object -> single object
),沒法說明我被容許使用箭頭從Monoid中容許的一個類型對象參數化任意數量的monoid值。 endo,〜identity等價定義忽略了functor的類型值以及最內層的「payload」層的類型和值。 所以,在函數類型匹配的任何狀況下,等價返回true
(例如, Nothing -> Just * -> Nothing
等同於Just * -> Just * -> Just *
由於它們都是Maybe -> Maybe -> Maybe
)。
邊欄:〜外面是概念性的,可是fa
最左邊的符號。 它還描述了「Haskell」首先讀入的內容(大圖); 所以Type類型與「類型值」相關。 編程中的層(參考鏈)之間的關係在類別中不易相關。 Set的類別用於描述類型(Int,Strings,Maybe Int等),其中包括Functor類別(參數化類型)。 參考鏈:Functor Type,Functor值(Functor集合的元素,例如,Nothing,Just),以及每一個functor值指向的其餘全部內容。 在類別中,關係的描述不一樣,例如, return :: a -> ma
被認爲是從一個Functor到另外一個Functor的天然轉換,與目前爲止提到的任何內容都不一樣。
回到主線程,總而言之,對於任何已定義的張量積和中性值,該語句最終描述了一個由其矛盾結構產生的驚人強大的計算結構:
:: List
); 靜態的 fold
,沒有說明有效載荷) 在Haskell中,澄清聲明的適用性很重要。 這種結構的強大功能和多功能性與monad 自己徹底無關。 換句話說,構造不依賴於monad的獨特之處。
當試圖弄清楚是否構建具備共享上下文的代碼以支持相互依賴的計算時,與能夠並行運行的計算相比,這個臭名昭着的語句,與其描述的同樣,並非選擇應用,箭頭和Monads,而是描述它們是多少相同。 對於手頭的決定,聲明沒有實際意義。
這常常被誤解。 該陳述繼續描述join :: m (ma) -> ma
做爲monoidal endofunctor的張量積。 可是,它沒有明確說明在本聲明的背景下, (<*>)
也能夠如何選擇。 它確實是一個六/半打的例子。 組合價值的邏輯徹底相同; 相同的輸入從每一個輸出生成相同的輸出(與Int的Sum和Product monoids不一樣,由於它們在組合Ints時會產生不一樣的結果)。
那麼,回顧一下:endofunctors類別中的monoid描述:
~t :: m * -> m * -> m * and a neutral value for m *
(<*>)
和(>>=)
都提供對兩個m
值的同時訪問,以便計算單個返回值。 用於計算返回值的邏輯徹底相同。 若是不是函數的不一樣形狀它們參數化( f :: a -> b
與k :: a -> mb
)和參數的位置具備相同的計算返回類型(即a -> b -> b
與b -> a -> b
分別爲每一個),我懷疑咱們能夠對幺半羣邏輯(張量積)進行參數化,以便在兩種定義中重複使用。 做爲一個重點的練習,嘗試並實現~t
,最終獲得(<*>)
和(>>=)
取決於你決定如何定義它forall ab
。
若是個人最後一點至少在概念上是真的,那麼它解釋了Applicative和Monad之間的精確且惟一的計算差別:它們參數化的函數。 換句話說,不一樣的是外部對這些類型的類的實現。
總而言之,根據我本身的經驗,Mac Lane的臭名昭着的引用提供了一個偉大的「goto」模因,這是我在引導個人方式經過類別時引用的指南,以更好地理解Haskell中使用的習語。 它成功地捕獲了在Haskell中很是容易訪問的強大計算能力的範圍。
然而,有點諷刺的是我第一次誤解了陳述在monad以外的適用性,以及我但願在這裏傳達的內容。 它所描述的一切都證實了Applicative和Monads(以及其餘箭頭)之間的類似之處。 它沒有說的正是它們之間的微小但有用的區別。
- E.
這裏的答案在定義monoids和monad方面作得很是出色,可是,他們彷佛仍然沒有回答這個問題:
在一個不過重要的注意事項上,這是真的,若是是這樣,你能給出一個解釋(但願有一個能夠被沒有Haskell經驗的人理解的解釋)嗎?
這裏缺乏的問題的關鍵是「幺半羣」的不一樣概念,更確切地說是所謂的分類 - 幺半羣中的幺半羣。 可悲的是,麥克萊恩的書自己使人困惑 :
總而言之,在一個單子
X
是正好在endofunctors的類別幺半羣X
,具備產品×
由endofunctors和單元由身份endofunctor設定的組合物替代。
爲何這使人困惑? 由於它沒有定義什麼是X
「endofunctors類別中的monoid」。 相反,這句話建議在全部endofunctor集合中使用monoid ,將functor組合做爲二元運算,將標識函子做爲幺半單元。 哪一個工做徹底正常,並變成一個monoid任何包含身份仿函數的endofunctor的子集,並在仿函數組合下關閉。
然而,這不是正確的解釋,這本書在那個階段沒有說清楚。 Monad f
是固定的 endofunctor,而不是在組合下關閉的endofunctors的子集。 一種常見的結構是使用f
經過取該組全部的以產生獨異k
倍組合物f^k = f(f(...))
的f
與自己,包括k=0
對應於身份f^0 = id
。 如今,對於全部k>=0
,全部這些冪的集合S
確實是一個「monoid」,其中產品×由endofunctors的組成和由身份endofunctor設置的單位替換。
可是:
S
能夠爲任何仿函數f
定義,甚至能夠爲X
任何自映射字面定義。 它是由f
生成的幺半羣。 S
的幺半羣結構與f
或不是monad沒有任何關係。 而且爲了使事情更加混亂,正如您從目錄中能夠看到的那樣,「monoid in monoidal category」的定義出如今本書的後面。 然而,理解這個概念對理解與monad的聯繫相當重要。
關於Monoids的第七章(比Monad第六章更晚),咱們發現所謂的嚴格幺半羣類別的定義爲三元組(B, *, e)
,其中B
是一個類別, *: B x B-> B
bifunctor (關於每一個組件的functor與其餘組件固定)和e
是B
的單元對象,知足關聯性和單位定律:
(a * b) * c = a * (b * c) a * e = e * a = a
對於任何對象a,b,c
的B
任何態射,和相同的標識a,b,c
與e
替換id_e
,身份態射e
。 如今是指導觀察,在咱們感興趣的狀況下,若是B
是endofunctors類別X
與天然變換爲態射, *
函子組成和e
身份仿函數,全部這些法律都滿意,由於能夠直接驗證。
隨之而來後的書是「輕鬆」 monoidal範疇 ,在法律只能容納模知足所謂的連貫關係 ,但它不是咱們的endofunctor類別的狀況下,重要的一些固定的天然變換的定義。
最後,在第七章第3節「幺半羣」中,給出了實際的定義:
幺半羣類別中的幺半羣
c
(B, *, e)
是B
的對象,帶有兩個箭頭(態射)
mu: c * c -> c nu: e -> c
製做3個圖表可交換。 回想一下,在咱們的例子中,這些是endofunctor類別中的態射,它們是對應於精確join
和return
monad的天然變換。 當咱們使組合*
更明確時,鏈接變得更清晰,用c^2
替換c * c
,其中c
是咱們的monad。
最後,請注意3個交換圖(在幺半羣類別中的幺半羣的定義中)是針對通常(非嚴格)幺半羣類別編寫的,而在咱們的例子中,做爲幺半羣類別的一部分而出現的全部天然變換實際上都是身份。 這將使圖表與monad定義中的圖表徹底相同,從而使對應完成。
總之,根據定義,任何monad都是endofunctor,所以是endofunctor類別中的對象,其中monadic join
和return
運算符知足特定(嚴格)monoidal類別中monoid的定義。 反之亦然,在幺半羣類型的endofunctor中的任何幺半羣定義爲由對象和兩個箭頭組成的三元組(c, mu, nu)
,例如在咱們的狀況下的天然變換,知足與monad相同的定律。
最後,請注意(經典)幺半羣與幺半羣類別中更通常的幺半羣之間的關鍵區別。 上面的兩個箭頭mu
和nu
再也不是二元運算和集合中的單位。 相反,你有一個固定的endofunctor c
。 儘管書中有使人困惑的評論,可是單獨的仿函數組合*
和身份仿函數並不能提供monad所需的完整結構。
另外一種方法是與集合A
的全部自映射的標準幺半羣C
進行比較,其中二元運算是組合,能夠看出將標準笛卡爾積C x C
映射到C
。 傳遞到分類的monoid,咱們用functor組合*
替換笛卡爾積x
,而且二進制操做被替換爲從c * c
到c
的天然變換mu
,這是join
運算符的集合
join: c(c(T))->c(T)
對於每一個對象T
(編程中的類型)。 經典幺半羣中的身份元素,能夠用固定的一點集的地圖圖像識別,被return
算子的集合所取代
return: T->c(T)
但如今沒有更多的笛卡爾積,因此沒有元素對,於是沒有二元運算。