猿學(xué)-Haskell學(xué)習(xí)-monad

什么是Monad

Haskell是一門純函數(shù)式的語言琐旁,純函數(shù)的優(yōu)點(diǎn)是安全可靠缭保。函數(shù)輸出完全取決于輸入喻犁,不存在任何隱式依賴钞啸,它的存在如同數(shù)學(xué)公式般完美無缺皱炉∷ⅲ可是純函數(shù)因?yàn)楦艚^了外部環(huán)境朽缴,連最基本的輸入輸出都無法完成祟蚀。而?Monad?就是 Haskell 給出的解決方案瞎疼。但Monad?并不僅僅是 IO 操作的抽象科乎,它更是多種類似操作之間共性的抽象。所以 Monad 解決的問題并不局限在 IO 上贼急,像 Haskell 中的?Maybe?和?[]?都是?Monad茅茂。Haskell 中漂亮的錯(cuò)誤處理方式,?do?表示法和靈活的列表推導(dǎo)式 (list comprehension) 都算是?Monad?的貢獻(xiàn)太抓。

Monad?基本上是一種加強(qiáng)版的?Applicative Functor空闲,正如?Applicative Functor?是?Functor?的加強(qiáng)版一樣。所以在充分理解?Applicative Functor?的基礎(chǔ)上腻异,過渡到?Monad?其實(shí)是非常平滑的进副。

-- Monad的定義classMonadmwherereturn :: a -> m a? ? (>>=) :: m a -> (a -> m b) -> m b? ? (>>) :: m a -> m b -> m b? ? x >> y = x >>= \_ -> y? ? fail ::String-> m a? ? fail msg = error msg

return?跟其他語言中的?return?是完全不一樣的,它是一個(gè)把普通值包進(jìn)一個(gè) context 里面的函數(shù)悔常,并不是結(jié)束函數(shù)執(zhí)行的關(guān)鍵字影斑。其實(shí)等價(jià)于Applicative中的?pure

>>?忽略前面表達(dá)式的返回值机打,直接執(zhí)行當(dāng)前表達(dá)式矫户。

>>=?接受一個(gè) monadic value(也就是具有 context 的值,可以用裝有普通值的盒子來比喻)并且把它喂給一個(gè)接受普通值的函數(shù)残邀,并回傳一個(gè) monadic value皆辽。

=<<?和上面?>>=?功能一樣柑蛇,只是結(jié)合順序相反。

Monad 的原理

函數(shù)之間要協(xié)作驱闷,就必須以各種形式交互連接耻台。但如何隔離純函數(shù)與副作用函數(shù),同時(shí)又能讓兩類函數(shù)相互復(fù)用呢空另?

以 IO 操作為例子分析盆耽,為了充分隔離純函數(shù)與 IO 函數(shù),Haskell 中不能實(shí)現(xiàn)?IO Char -> Char?這樣一種輸入是 IO 類型返回值卻是普通類型的函數(shù)扼菠。否則副作用函數(shù)就能很容易變身為純函數(shù)了摄杂。事實(shí)上一旦參數(shù)中有 IO,返回值必有 IO循榆,這就保證了充分隔離析恢。

那如何讓純函數(shù)與 IO 函數(shù)相互復(fù)用呢?這就要靠 IO Monad 中定義的?return?和?>>=?這兩個(gè)函數(shù)了秧饮。return?(在 Haskell 中不是關(guān)鍵字映挂,只是一個(gè)函數(shù)名)的作用是將某個(gè)類型為?A?的值?a?提升(裝箱)為類型為?IO A?的值?Char -> IO Char?。有了這個(gè)函數(shù)后盗尸,純函數(shù)就可以通過?return?變成返回值為 IO 帶副作用的函數(shù)了袖肥。

有了提升而沒有下降操作,怎么復(fù)合?putChar :: Char -> IO()?與?getChar :: IO Char?呢振劳。 getChar 從 IO 讀取一個(gè)字符, putChar 把字符寫入 IO油狂。但?getChar?返回的是?IO Char?類型历恐,而?putChar?需要的是普通的?Char?類型,兩者不匹配怎么辦专筷??>>=?函數(shù)出馬了弱贼!?>>=?的類型是

IOa -> (a ->IOb) ->IOb

這樣?>>=?就可以連接?getChar?與?putChar?,把輸入寫到輸出中

getChar>>= putChar

可以看到?>>=?操作實(shí)際上是類型下降(或拆箱)操作磷蛹,同時(shí)執(zhí)行下降操作的函數(shù)返回值也必須是 IO 類型吮旅。這樣既充分隔離純函數(shù)與副作用函數(shù),又能讓函數(shù)相互復(fù)用味咳。通過?return?和?>>=?兩個(gè)平行世界 (范疇) 就有了可控的交流通道庇勃。

do 表示法

Haskell的 do 表示法實(shí)際上是Monad的語法糖:它給我們提供了一種不使用 (>>=) 和匿名函數(shù)來寫monadic代碼的方式。去除do語法糖的過程就是把它轉(zhuǎn)換為 (>>=) 和匿名函數(shù)槽驶。

do 表示法可以使用分號(hào)?;?和大括號(hào)?{ }?將語句分塊责嚷;但一般會(huì)使用一個(gè)表達(dá)式一行的方式,不同的作用域用不同的縮進(jìn)區(qū)分掂铐。

我們還是以IO 為例子罕拂,接受兩次的鍵盤輸入揍异,然后將兩次輸入的字符串合并成一個(gè)字符串,最后屏幕打印輸出爆班。?>>=?會(huì)接受前面表達(dá)式的值衷掷;>>則會(huì)忽略前面表達(dá)式的值;這里使用?return?實(shí)際它返回的仍然是IO String柿菩,因?yàn)镠askell會(huì)自動(dòng)類型推導(dǎo)得出戚嗅。monadic 的表達(dá)式代碼如下:

(++) <$> getLine <*> getLine >>= print >> return"over"111222>"111222">"over"

使用 do改寫,明顯更加清晰碗旅,和我們熟悉的命令式語言風(fēng)格差不多渡处。

<-?表示從monadic value中取出普通值,可以看成是拆開盒子取出所需要的值祟辟。

foo::IOStringfoo=dox <- getLine? ? y <- getLine? ? print (x ++ y)? ? return"over"

do語法對應(yīng)模式

do{e}? ? ? ? ? ? -> edo{e; es}? ? ? ? -> e >>do{es}do{letdecls; es} ->letdeclsindo{es}do{p <- e; es}? ? -> e >>= \p -> es

Monad 類型

來看一下幾個(gè)默認(rèn)的Monad類型医瘫,它們都必須實(shí)現(xiàn)?return,>>=,fail這幾個(gè)函數(shù)。

Maybe

中間任何一步只要有Nothing旧困,結(jié)果就提前返回Nothing醇份。沒有任何意外的情況才返回Just 值

-- Maybe 的 Monad instanceinstanceMonadMaybewherereturn x =JustxNothing>>= f =NothingJustx >>= f? = f x? ? fail _ =Nothing-- 實(shí)例Just3>>= (\x ->Nothing>>= (\y ->Just(show x ++ y)))>NothingJust3>>= (\x ->Just"!">>= (\y ->Just(show x ++ y)))>Just"3!"

使用 do 表示法寫成這樣:

foo::MaybeStringfoo=dox <-Just3y <-Just"!"Just(show x ++ y)

List

>>=?基本上就是接受一個(gè)有 context 的值吼具,把他喂進(jìn)一個(gè)只接受普通值的函數(shù)僚纷,并回傳一個(gè)具有 context 的值。[ ]?其實(shí)等價(jià)于 Nothing拗盒。

當(dāng)我們用?>>=?把一個(gè) list 喂給這個(gè)函數(shù)怖竭,lambda 會(huì)映射每個(gè)元素,會(huì)計(jì)算出一串包含一堆 list 的 list陡蝇,最后再把這些 list 壓扁痊臭,得到一層的 list。這就是我們得到 列表?list?處理 Mondic value 的過程登夫。

--list 的 Monad instanceinstanceMonad[]wherereturn x = [x]? ? xs >>= f = concat (map f xs)? ? fail _ = []-- 實(shí)例[3,4,5] >>= \x -> [x,-x]> [3,-3,4,-4,5,-5][1,2,3] >>= \x -> return (-x)> [-1,-2,-3]

list comprehension?也不過是?Monad?的語法糖

[1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)-- Monad[ (n,ch) | n <- [1,2], ch <- ['a','b'] ]-- list comprehension> [(1,'a'),(1,'b'),(2,'a'),(2,'b')]

list comprehension?的過濾基本上跟 guard 是一致的广匙。

[1..50] >>= (\x -> guard ('7' `elem` show x) >> return x)> [7,17,27,37,47]

用?do?改寫, 如果不寫最后一行?return x,那整個(gè) list 就會(huì)是包含一堆空?tuple?的 list恼策。

sevensOnly:: [Int]sevensOnly=dox <- [1..50]? ? guard ('7' `elem` show x)? ? return x-- 對應(yīng)的 list comprehension[ x | x <- [1..50], '7' `elem` show x ]> [7,17,27,37,47]

Either

在?Control.Monad.Error?里面有?Error的?Monad instance鸦致。

instance(Errore) =>Monad(Eithere)wherereturn x =RightxRightx >>= f = f xLefterr >>= f =Lefterr? ? fail msg =Left(strMsg msg)Right3>>= \x -> return (x +100) ::EitherStringInt>Right103

Monad 規(guī)則

return a >>= f == f a

== 左邊的表達(dá)式等價(jià)于右邊的表達(dá)式。如果僅僅是把一個(gè)值包裝到monad里面然后使用 (>>=) 調(diào)用的話涣楷,我們就沒有必要使用?return?分唾;這條規(guī)則對于我們的代碼風(fēng)格有著實(shí)際的指導(dǎo)意義:我們不應(yīng)該寫一些不必要的代碼;這條規(guī)則保證了簡短的寫法和冗余的寫法是等價(jià)的狮斗。

return3>>= (\x ->Just(x+100000))-- 和直接函數(shù)調(diào)用沒有區(qū)別

m >>= return == m

這一條規(guī)則對風(fēng)格也有好處:如果在一系列的action塊里面鳍寂,如果最后一句就是需要返回的正確結(jié)果,那么就不需要使用 return 了情龄;和第一條規(guī)則一樣迄汛,這條規(guī)律也能幫助我們簡化代碼捍壤。

Just"move on up">>= return-- 可以不需要 return

(m >>= f) >>= g == m >>= (\x -> f x >>= g)

當(dāng)我們用 >>= 把一串 monadic function 串在一起,他們的先后順序不應(yīng)該影響結(jié)果鞍爱。

而這不就是結(jié)合律嗎鹃觉?我們可以把那些子action提取出來組合成一個(gè)新action。

(<=<) 可以用來合成兩個(gè) monadic functions, 類似于普通函數(shù)結(jié)合(.)睹逃, 而(>=>) 表示結(jié)合順序相反盗扇。

(<=<) :: (Monadm) => (b -> m c) -> (a -> m b) -> (a -> m c)f<=< g = (\x -> g x >>= f)-- 普通函數(shù)結(jié)合(.)letf = (+1) . (*100)f4>401-- 合成monadic functions (<=<)letg = (\x -> return (x+1)) <=< (\x -> return (x*100))Just4>>= g>Just401-- 也可以將 monadic 函數(shù)用foldr,id 和(.)合成 letf = foldr (.) id [(+1),(*100),(+1)]f1>201

Monad 的 (->) r 形態(tài)

(->) r?不只是一個(gè)?functor?和?applicative functor,同時(shí)也是一個(gè)?monad沉填。

每一個(gè)?monad?都是個(gè)?applicative functor疗隶,而每一個(gè)?applicative functor也都是一個(gè)?functor。盡管?moand?有?functor?跟?applicative functor的性質(zhì)翼闹,但他們不見得有?Functor?跟?Applicative?的 instance 定義斑鼻。

instanceMonad((->) r)wherereturn x = \_ -> x? ? h >>= f = \w -> f (h w) w

Monad 輔助函數(shù)

帶下劃線函數(shù)等價(jià)于不帶下劃線的函數(shù), 只是不返回值

>>= :: m a -> (a -> m b) -> m b=<< :: (a -> m b) -> m a -> m bform:: t a -> (a -> m b) -> m (t b)form_:: t a -> (a -> m b) -> m ()mapM:: (a -> m b) -> t a -> m (t b)mapM_:: (a -> m b) -> t a -> m ()filterM:: (a -> mBool) -> [a] -> m [a]foldM:: (b -> a -> m b) -> b -> t a -> m bsequence:: t (m a) -> m (t a)sequence_:: t (m a) -> m ()liftM:: (a1 -> r) -> m a1 -> m rwhen::Bool-> f () -> f ()join:: m (m a) -> m a

其中在 IO 中經(jīng)常用到的一些函數(shù)

sequence

sequence?接受一串 I/O action,并回傳一個(gè)會(huì)依序執(zhí)行他們的 I/O action猎荠。運(yùn)算的結(jié)果是包在一個(gè) I/O action 的一連串 I/O action 的運(yùn)算結(jié)果坚弱。

main=doa <- getLine? ? b <- getLine? ? c <- getLine? ? print [a,b,c]

其實(shí)可以寫成

main=dors <- sequence [getLine, getLine, getLine]? ? print rs

一個(gè)常見的使用方式是我們將?print?或?putStrLn?之類的函數(shù) map 到串列上。

sequence(map print [1,2,3,4,5])12345[(),(),(),(),()]

mapM?跟?mapM_

由于對一個(gè)串列 map 一個(gè)回傳 I/O action 的函數(shù)关摇,然后再?sequence?這個(gè)動(dòng)作太常用了荒叶。所以函式庫中提供了?mapM?跟?mapM_mapM接受一個(gè)函數(shù)跟一個(gè)串列输虱,將對串列用函數(shù) map 然后 sequence 結(jié)果些楣。mapM_?也作同樣的事,只是他把運(yùn)算的結(jié)果丟掉而已宪睹。在我們不關(guān)心 I/O action 結(jié)果的情況下戈毒,mapM_?是最常被使用的。

mapMprint [1,2,3]123[(),(),()]mapM_print [1,2,3]123

form?和?form_?與?mapM?和?mapM_?類似横堡,不過只是把列表參數(shù)提前。

還有一些是在?monad?中定義冠桃,且等價(jià)于?functor?或?applicative functor?中所具有的函數(shù)命贴。

liftM

liftM?跟?fmap?等價(jià), 也有?liftM3liftM4?跟?liftM5

liftM:: (Monadm) => (a -> b) -> m a -> m bliftMf m = m >>= (\x -> return (f x))liftM(*2) [1,2]> [2,4]

ap

ap?基本上就是 **<*>**食听,只是他限制在 Monad 上而不是 Applicative 上胸蛛。

ap:: (Monadm) => m (a -> b) -> m a -> m bapmf m =dof <- mf? ? x <- m? ? return (f x)ap[(*2)] [1,2,3]> [2,4,6]

join

m >>= f?永遠(yuǎn)等價(jià)于?join (fmap f m)?這性質(zhì)非常有用

join:: (Monadm) => m (m a) -> m ajoin(Just(Just9))>Just9join[[1,2,3],[4,5,6]]-- 對于 list 而言 join 不過就是 concat> [1,2,3,4,5,6]

filterM

filterM,除了能做 filter 的動(dòng)作樱报,同時(shí)還能保有 monadic context葬项。

filterM:: (Monadm) => (a -> mBool) -> [a] -> m [a]filterM(\x -> return (x >2)) [1,2,3,4]> [3,4]

foldM

foldl?的 monadic 的版本叫做?foldM

foldM:: (Monadm) => (a -> b -> m a) -> a -> [b] -> m afoldM(\x y -> return (x + y))0[1,2,3]>6

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市迹蛤,隨后出現(xiàn)的幾起案子民珍,更是在濱河造成了極大的恐慌襟士,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嚷量,死亡現(xiàn)場離奇詭異陋桂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蝶溶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門嗜历,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抖所,你說我怎么就攤上這事梨州。” “怎么了田轧?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵暴匠,是天一觀的道長。 經(jīng)常有香客問我涯鲁,道長巷查,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任抹腿,我火速辦了婚禮岛请,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘警绩。我一直安慰自己崇败,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布肩祥。 她就那樣靜靜地躺著后室,像睡著了一般。 火紅的嫁衣襯著肌膚如雪混狠。 梳的紋絲不亂的頭發(fā)上岸霹,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機(jī)與錄音将饺,去河邊找鬼贡避。 笑死,一個(gè)胖子當(dāng)著我的面吹牛予弧,可吹牛的內(nèi)容都是我干的刮吧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼掖蛤,長吁一口氣:“原來是場噩夢啊……” “哼杀捻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚓庭,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤致讥,失蹤者是張志新(化名)和其女友劉穎仅仆,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拄踪,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蝇恶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了惶桐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撮弧。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖姚糊,靈堂內(nèi)的尸體忽然破棺而出贿衍,到底是詐尸還是另有隱情,我是刑警寧澤救恨,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布贸辈,位于F島的核電站,受9級特大地震影響肠槽,放射性物質(zhì)發(fā)生泄漏擎淤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一秸仙、第九天 我趴在偏房一處隱蔽的房頂上張望嘴拢。 院中可真熱鬧,春花似錦寂纪、人聲如沸席吴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽孝冒。三九已至,卻和暖如春拟杉,著一層夾襖步出監(jiān)牢的瞬間庄涡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工搬设, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留穴店,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓焕梅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親卦洽。 傳聞我的和親對象是個(gè)殘疾皇子贞言,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

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

  • 原文地址:Haskell學(xué)習(xí)-monad 什么是Monad Haskell是一門純函數(shù)式的語言,純函數(shù)的優(yōu)點(diǎn)是安全...
    jeffzhong閱讀 3,707評論 0 2
  • 1.Functor, Applicative, 和Monad,都是deal with有context的值的類型(t...
    tigerhy1閱讀 407評論 0 1
  • 在C語言中,五種基本數(shù)據(jù)類型存儲(chǔ)空間長度的排列順序是: A)char B)char=int<=float C)ch...
    夏天再來閱讀 3,341評論 0 2
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理阀蒂,服務(wù)發(fā)現(xiàn)该窗,斷路器弟蚀,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • 我們每天度過的稱之為日常的生活义钉,其實(shí)是一個(gè)個(gè)奇跡的連續(xù)也說不定。 ——《日彻骐龋》 關(guān)于假期清單捶闸,嘗試做了幾份,最終都...
    Vicky趙ING閱讀 242評論 0 1