什么是Monad?

最近比較巧合的接觸了Functional programming坐昙,經常接觸到Monad的概念(在Haskell和F#中),然而除了知道這個概念很難明白以外其他都不太清楚芋忿,粗略地看了幾篇相關文章講的很晦澀難懂炸客。所以決定花一段時間將這個FP的奇怪概念搞個明白。

什么是monad?

wikipedia

太長了盗飒,懶得看

恩我也是這么想的嚷量。。所以我一直沒看逆趣,我在這里嘗試著來用簡單一些的文字說明一下Monad的概念,有可能每段都有錯誤嗜历,謹慎啊宣渗。

Monad是一個FP中的專有名詞,是一個含有變量的類梨州,monad是Monad這個類的實例痕囱。這個類的作用是把一系列操作連接在一起。沒錯你可能想到do關鍵字暴匠,比如最簡單的IO實例:

do

putStrLn "What is your name?"

name <- getLine

putStrLn ("Welcome, " ++ name ++ "!")

在這里do之后的list里面我們做了三件事情鞍恢,打印,讀取IO輸入,輸出結果帮掉。在這三句話中間傳遞了參數name弦悉,這是一個state,這個state存在的意義是使得我們能夠以pure functional的方式執(zhí)行這三句話蟆炊。

然而do關鍵字是一個語法糖稽莉,在這個語法糖的背后是一個>>=,也就是bind操作符在支持這個操作的連續(xù)性涩搓。那么在這里說污秆,Monad就是一個支持>>=操作符的類,就是一個能夠連接多個operation的類昧甘。沒錯剛才那段代碼就是一個monad良拼,就是一段由幾個function組成的control flow,bind操作符的作用就是讀取左邊操作的結果并且讓右邊操作能夠使用充边。很簡單

這說的太簡單了将饺,肯定是錯的

是說的很簡單,但是不能說是錯的吧痛黎。予弧。舉個例子,假如有個沒有概念的人問你“什么是函數”湖饱,怎么回答掖蛤?一段映射?讀取幾個參數輸出幾個參數的代碼井厌?這么簡單的概念你怎么使別人相信函數在編程過程中的重要作用呢蚓庭,很難吧。Java中我們當然可以寫一個函數仅仆,讀取一個int返回一個int器赞,這嚴格的遵守了數學函數的定義,但是同時這個函數還可以做很多其他的事情墓拜,比如打印出來港柜,比如進一個死循環(huán),但是這些不是pure function能做的咳榜,haskell這樣的語言中function就應該讀取一個值返回一個值夏醉,它對程序的影響只能體現(xiàn)在它的返回值上∮亢“什么是函數”這個問題的答案大概長什么樣子我想大家心里應該有數畔柔。

Monad能發(fā)揮巨大作用,不是因為它的定義太復雜臣樱,是因為他不只是簡單的定義靶擦,而是可以延伸出無數個種類腮考。沒錯bind操作符的確就是簡單地把參數從左邊傳給右邊,能包含bind操作符的都是monad玄捕,但是monad還可以同時做很多其他的事情踩蔚,做的事情不一樣monad的作用也不一樣。換句話說桩盲,不同monad賦予了>>=不同的意義寂纪。

舉一個不是我想出來的例子,以下代碼是javascript的幾個函數赌结。

var sine = function(x) { return Math.sin(x) };

var cube = function(x) { return x * x * x };

var sineCubed = cube(sine(x));

sineCubed是一個組合函數捞蛋,可以很簡單的把它在Haskell中寫出來。然而這個時候我們隊sineCubed有個特殊的要求柬姚,要求它能夠打印出來自己運行時的值拟杉。javascript中間在函數里加一句console.log即可,但是Haskell中間呢量承?沒辦法在sine:: Number -> Number這個函數中間加一句打印搬设,那樣違反了pure function的原則。那么我們只能在返回值中體現(xiàn)出來:

var sine = function(x) {

return [Math.sin(x), 'sine was called.'];

};

var cube = function(x) {

return [x * x * x, 'cube was called.'];

};

那么sineCubed此時應該怎么寫撕捍?假設還是之前的寫法拿穴,那么我們會發(fā)現(xiàn)cube函數需要讀取一個數組了,無法執(zhí)行忧风。這時候就需要多做一步處理默色,假設這個時候我在做一個作業(yè),只要完成作業(yè)即可狮腿,那我可能就是sineCubed中cube只讀取sine返回值的第一個參數腿宰,或者改變cube的簽名為cube :: (Number,String) -> (Number,String)野蠻地完成任務(語言穿越了,只是為了表達意思)缘厢。這顯然不是best practice吃度,更優(yōu)雅的方法是什么?沒錯這位同學答對了贴硫,就是對參數進行一個包裝和解包裝的工作椿每,我們需要一個工具能夠做這個事情。

首先需要一個unit夜畴,它讀取一個Number拖刃,將這個number放在一個container里,返回一個(Number,String)贪绘。

// unit :: Number -> (Number,String)

var unit = function(x) { return [x, ''] };

unit函數使得原本簡單的返回值可以被包裝成包含了其他信息的值。然后在lift函數中我們用到了unit:

// lift :: (Number -> Number) -> (Number -> (Number,String))

var lift = function(f) {

return function(x) {

return unit(f(x));

};

};

lift的簽名是讀取一個函數央碟,返回一個函數税灌,它將一個簡單函數"lift"到了一個包含了一個其他信息的函數均函。要做sineCubed,我們還需要一個函數能夠組合幾個函數菱涤,這是compose做的事情苞也,它簡單地把兩個函數復合起來≌掣眩可以想象還需要一個bind如迟,它使得原本的sine和cube的函數簽名能夠被修改成想要的類型。這幾個抽象的函數概念就組成了一個monad攻走,實際上bind和unit就組成了一個monad殷勘,剛剛做的事情其實就是Haskell的Writer monad所做的事情。打開這個鏈接看看昔搂,應該你就能懂了玲销。

這么說,monad其實是一種design pattern?

我個人覺得拿javascript去形容Haskell中的概念是一件容易誤導人的事情摘符,之前在一篇很長的blog中也看到說“不要用其他語言的思維去考慮函數式語言”贤斜。說monad是一種design pattern是有一定道理的,假設你在程序中需要一個函數接受一類輸入逛裤,得到另外一類的輸出瘩绒,那么就要考慮用到bind和unit這樣的函數,unit函數包裝參數的類得到需要的另外一種類型带族,bind函數修改原函數使得它能夠接受自己返回的函數類型锁荔。這樣做的好處是可以在達到目的的同時,避免對原來的代碼做出“毀滅性”的徹底修改炉菲。

然而之上說的只是一種monad類型堕战,并不適用到全部范圍。我們來看看其它幾種monad:

  • 在一系列操作中拍霜,每一步都返回一個success/failure的標志嘱丢,只有success才執(zhí)行下一步,failure則自動終止祠饺。這是Failure Monad越驻。

  • 將返回標志改成Exception的處理,這是Error Monad道偷, Exception Monad缀旁,如何處理完全可以自定義。

  • 每一步返回多個結果勺鸦,在下一步遍歷這些結果并巍,進行篩選或者處理,這是List Monad换途。

  • 每一步操作都是針對state的一個action懊渡,下一步操作只從上一步操作返回的world status得到信息進行操作刽射,bind操作使得IO的side effects能夠保證按順序處理,這是I/O Monad剃执。(這里說的太籠統(tǒng)誓禁,最好再看看I/O Monad的說明。肾档。)
    更合適的說摹恰,Monad是一個通用的將各個函數作為組建搭建起來的“積木”,這個積木有兩個基本部件"return"和">>="怒见,而且這兩個部件滿足一些特定的組合性質俗慈,那么我就可以說我搭建的是一個monad:

  1. (return x) >>= f == f x
  2. m >>= return == m
  3. (m >>= f) >>= g == m >>= (\x -> f x >>= g)

orz...

這些事情,不用Monad當然也能做到速种,但是Monad的意義是使得達到這些目的的方法簡單很多姜盈,只需要去定義>>=做什么事情即可達到目的。

打字好累配阵。馏颂。stackoverflow的這個鏈接有很多大牛講解了自己對Monad的理解,看完這篇日志之后再去看這個鏈接可能會稍微多理解一些東西(或者可以發(fā)現(xiàn)我說錯了那麻煩告訴我一下=棋傍,=)

第一次在簡書上寫日志救拉,markdown的設置弄了很久。瘫拣。希望這篇日志能夠對大家有點小幫助

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末亿絮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子麸拄,更是在濱河造成了極大的恐慌派昧,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拢切,死亡現(xiàn)場離奇詭異蒂萎,居然都是意外死亡,警方通過查閱死者的電腦和手機淮椰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門五慈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人主穗,你說我怎么就攤上這事窒盐∥酰” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵猾昆,是天一觀的道長卿嘲。 經常有香客問我魂贬,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任灯抛,我火速辦了婚禮金赦,結果婚禮上音瓷,老公的妹妹穿的比我還像新娘。我一直安慰自己夹抗,他們只是感情好绳慎,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著漠烧,像睡著了一般杏愤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上已脓,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天珊楼,我揣著相機與錄音,去河邊找鬼度液。 笑死厕宗,一個胖子當著我的面吹牛,可吹牛的內容都是我干的堕担。 我是一名探鬼主播已慢,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼霹购!你這毒婦竟也來了佑惠?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤齐疙,失蹤者是張志新(化名)和其女友劉穎膜楷,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體贞奋,經...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡赌厅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了忆矛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片察蹲。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖催训,靈堂內的尸體忽然破棺而出洽议,到底是詐尸還是另有隱情,我是刑警寧澤漫拭,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布亚兄,位于F島的核電站,受9級特大地震影響采驻,放射性物質發(fā)生泄漏审胚。R本人自食惡果不足惜匈勋,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望膳叨。 院中可真熱鬧洽洁,春花似錦、人聲如沸菲嘴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽龄坪。三九已至昭雌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間健田,已是汗流浹背烛卧。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留妓局,地道東北人总放。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像跟磨,于是被迫代替她去往敵國和親间聊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內容