Monad

Monad不就是個自函子范疇上的幺半群,這有什么難理解的(A monad is just a monoid in the category of endofunctors)
—— Phillip Wadler

自函子(Endofunctor)

什么是函數(shù)(Function)冰啃?
函數(shù)表達的映射關(guān)系在類型上體現(xiàn)在特定類型(proper type)之間的映射刘莹。

什么是自函數(shù)(Endofunction)?

identity :: Number -> Number

自函數(shù)就是把類型映射到自身類型点弯。函數(shù)identity是一個自函數(shù)的特例,它接收什么參數(shù)就返回什么參數(shù)狼钮,所以入?yún)⒑头祷刂挡粌H類型一致捡絮,而且值也相同。

接下來涎拉,回答什么是自函子(Endofunctor)之前的圆,我們先弄清什么是函子(Functor)?

函子有別于函數(shù)越妈,函數(shù)描述的是特定類型(proper type)之間的映射梅掠,而函子描述的是范疇(category)之間的映射。

那什么是范疇(category)瓤檐?

我們把范疇看做一組類型及其關(guān)系態(tài)射(morphism)的集合娱节。包括特定類型及其態(tài)射,比如Int谴古、String、Int -> String汇陆;高階類型及其態(tài)射带饱,比如List[Int]、List[String]勺疼、List[Int] -> List[String]

接下來看看函子是如何映射兩個范疇的酪耕,見下圖:


范疇

圖中范疇C1和范疇C2之間有映射關(guān)系轨淌,C1中Int映射到C2中的List[Int],C1中String映射到C2中的List[String]盟步。除此之外梳虽,C1中的關(guān)系態(tài)射Int -> String也映射到C2中的關(guān)系List[Int] -> List[String]態(tài)射上。

換句話說谷炸,如果一個范疇內(nèi)部的所有元素可以映射為另一個范疇的元素禀挫,且元素間的關(guān)系也可以映射為另一個范疇元素間關(guān)系,則認為這兩個范疇之間存在映射描孟。所謂函子就是表示兩個范疇的映射砰左。

澄清了函子的含義,那么如何在程序中表達它缠导?

在Haskell中,函子是在其上可以map over的東西憋他。稍微有一點函數(shù)式編程經(jīng)驗竹挡,一定會想到數(shù)組(Array)或者列表(List),確實如此梯码。不過耸序,在我們的例子中,List并不是一個具體的類型坎怪,而是一個類型構(gòu)造子。舉個例子嘁酿,構(gòu)造List[Int]男应,也就是把Int提升到List[Int],記作Int -> List[Int]游桩。這表達了一個范疇的元素可以映射為另一個范疇的元素耐朴。

List具有map方法,不妨看看map的定義:

f :: A -> B
map :: f -> List[A] -> List[B]

具體到我們的例子當(dāng)中铐刘,就有:

f :: Int -> String
map :: f -> List[Int] -> List[String]

展開來看:

map :: Int -> String -> List[Int] -> List[String]

map的定義清晰地告訴我們:Int -> String這種關(guān)系可以映射為List[Int] -> List[String]這種關(guān)系影晓。這就表達了元素間的關(guān)系也可以映射為另一個范疇元素間關(guān)系。

所以類型構(gòu)造器List[T]就是一個函子疤祭。

理解了函子的概念饵婆,接著繼續(xù)探究什么是自函子。我們已經(jīng)知道自函數(shù)就是把類型映射到自身類型,那么自函子就是把范疇映射到自身范疇。

自函子是如何映射范疇的续挟,見下圖:


Identity自函子范疇

圖中表示的是一個將范疇映射到自身的自函子侥衬,而且還是一個特殊的Identity自函子。為什么這么說直颅?從函子的定義出發(fā)怀樟,我們考察這個自函子,始終有List[Int] -> List[Int]以及List[Int] -> List[String] -> List[Int] -> List[String]這兩種映射往堡。
我們表述成:

類型List[Int]映射到自己
態(tài)射f :: List[Int] -> List[String]映射到自己

我們記作:

F(List[Int]) = List[Int]
F(f) = f
其中虑灰,F(xiàn)是Functor.

除了Identity的自函子,還有其它的自函子穆咐,見下圖:


自函子范疇

圖中的省略號代表這些范疇可以無限地延伸下去对湃。我們在這個大范疇所做的所有映射操作都是同一范疇內(nèi)的映射,自然這樣的范疇就是一個自函子的范疇熟尉。

我們記作:

List[Int] -> List[List[Int]]
List[Int] -> List[String] -> List[List[Int]] -> List[List[String]]
...

所以List[Int]斤儿、List[List[Int]]、...往果、List[List[List[...]]]及其之間的態(tài)射是一個自函子的范疇。


幺半群

[幺半群][1]是一個帶有二元運算 : M × M → M 的集合 M 堕油,其符合下列公理:
結(jié)合律:對任何在 M 內(nèi)的a、b卜录、c眶明, (a
b)c = a(bc) 。
單位元:存在一在 M 內(nèi)的元素e搜囱,使得任一于 M 內(nèi)的 a 都會符合 a
e = e*a = a 。

接著我們看看在自函子的范疇上绊汹,怎么結(jié)合幺半群的定義得出Monad的扮宠。

假設(shè)我們有個cube函數(shù),它的功能就是計算每個數(shù)的3次方浴栽,函數(shù)簽名如下:

cube :: Number -> Number

現(xiàn)在我們想在其返回值上添加一些調(diào)試信息轿偎,所以返回一個元組(Tuple),第二個元素代表調(diào)試信息萝玷。函數(shù)簽名如下:

f :: Number -> (Number,String)

入?yún)⒑统鰠⒉灰恢吕バ觯@會產(chǎn)生什么影響?我們看看幺半群的定義中規(guī)定的結(jié)合律仓蛆。對于函數(shù)而言看疙,結(jié)合律就是將函數(shù)以各種結(jié)合方式嵌套起來調(diào)用。我們將常用的compose函數(shù)看作此處的二元運算能庆。

var compose = function(f, g) {
  return function(x) {
    return f(g(x));
  };
};

compose(f, f)

從函數(shù)簽名可以很容易看出,右邊的f運算的結(jié)果是元組弥搞,而左側(cè)的f卻是接收一個Number類型的函數(shù),它們是彼此不兼容的船逮。

有什么好辦法能消除這種不兼容性粤铭?結(jié)合前面所講,cube是一個自函數(shù)Number -> Number,而元組(Number,String)在Hask范疇是一個自函子伪煤,理由如下:

F Number = (Number,String) 
F Number -> Number = (Number,String) -> (Number,String)

假如輸入和輸出都是元組,結(jié)果會如何呢职烧?

fn :: (Number,String) -> (Number,String)

compose(fn, fn)  

這樣是可行的防泵!在驗證滿足結(jié)合律之前,我們引入一個bind函數(shù)來輔助將f提升成fn.

f :: Number -> (Number,String) => fn :: (Number,String) -> (Number,String)
注: 在Haskell中稱為 liftM
var bind = function(f) {
  return function F(tuple) {
    var x  = tuple[0],
        s  = tuple[1],
        fx = f(x),
        y  = fx[0],
        t  = fx[1];

    return [y, s + t];
  };
};

我們來實現(xiàn)元組自函子范疇上的結(jié)合律:

var cube = function(x) {
  return [x * x * x, 'cube was called.'];
};

var sine = function(x) {
  return [Math.sin(x), 'sine was called.'];
};

var f = compose(compose(bind(sine), bind(cube)), bind(cube));
f([3, ''])

var f1 = compose(bind(sine), compose(bind(cube), bind(cube)));
f1([3,''])
>>>
[0.956, 'cube was called.cube was called.sine was called.']
[0.956, 'cube was called.cube was called.sine was called.']

這里f和f1代表的調(diào)用順序產(chǎn)生同樣的結(jié)果足删,說明元組自函子范疇滿足結(jié)合律锁右。

那如何找到這樣一個e咏瑟,使得a*e=e*a=a,此處的*compose運算

// unit :: Number -> (Number,String)
var unit = function(x) { return [x, ''] };

var f = compose(bind(sine), bind(cube));

compose(f, bind(unit)) = compose(bind(unit), f) = f

這里的bind(unit)就是e了码泞。

到這里,思路逐步清晰了领铐。在Haskell這類的強類型語言中劈狐,我們甚至可以組裝自己的Tuple Monad。如下:

type Tuple(Number,String)
flatmap :: Tuple -> Number -> Tuple -> Tuple
unit :: Number -> Tuple
map :: Tuple >>= unit
    :: Tuple -> Number -> Number -> Tuple

//compose
// flatmap 即 bind莲兢,中綴表達式一般是 >>=
Tuple >>= (Number -> Tuple) >>= (Number -> Tuple)

Monads for functional programming一書中介紹說monad是一個三元組(M, unit, *),對應(yīng)此處就是(Tuple, unit, bind).

參考鏈接:

  1. Translation from Haskell to JavaScript of selected portions of the best introduction to monads I've ever read
  2. 我所理解的monad
  3. Monads for functional programming
  4. Functor, Applicative, Monad
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末收班,一起剝皮案震驚了整個濱河市谒兄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌邻耕,老刑警劉巖燕鸽,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件啊研,死亡現(xiàn)場離奇詭異,居然都是意外死亡党远,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門氛驮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來济似,“玉大人,你說我怎么就攤上這事磷脯∶淦ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵俩功,是天一觀的道長碰声。 經(jīng)常有香客問我,道長蔓罚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任郑象,我火速辦了婚禮茬末,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘击奶。我一直安慰自己责掏,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著喜爷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪术幔。 梳的紋絲不亂的頭發(fā)上湃密,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天泛源,我揣著相機與錄音,去河邊找鬼达箍。 笑死,一個胖子當(dāng)著我的面吹牛硬纤,可吹牛的內(nèi)容都是我干的赃磨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼溪王,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了在扰?” 一聲冷哼從身側(cè)響起芒珠,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎裹芝,沒想到半個月后娜汁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡掐禁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年傅事,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片障本。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡响鹃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出买置,到底是詐尸還是另有隱情忿项,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布洒擦,位于F島的核電站怕膛,受9級特大地震影響熟嫩,放射性物質(zhì)發(fā)生泄漏褐捻。R本人自食惡果不足惜椅邓,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一昧狮、第九天 我趴在偏房一處隱蔽的房頂上張望景馁。 院中可真熱鬧,春花似錦逗鸣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至糠悯,卻和暖如春纫普,著一層夾襖步出監(jiān)牢的瞬間假栓,已是汗流浹背寻行。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拌蜘,地道東北人杆烁。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像简卧,于是被迫代替她去往敵國和親兔魂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容