軟件最基本的數(shù)據(jù),就是各種值(value)。
處理值的一系列操作,可以封裝成函數(shù)。輸入一個值,會得到另一個值。上圖的"(+3)"就是一個函數(shù),對輸入的值加上3,再輸出。
函數(shù)很像漏斗,上面進(jìn)入一個值,下面出來一個值。
函數(shù)可以連接起來使用,一個函數(shù)接著另一個函數(shù)。
函數(shù)還可以依次處理數(shù)據(jù)集合的每個成員。
說完了函數(shù),再來看第二個概念:數(shù)據(jù)類型(type)。
數(shù)據(jù)類型就是對值的一種封裝,不僅包括值本身,還包括相關(guān)的屬性和方法。上圖就是2的封裝,從此2就不是一個單純的值,而是一種數(shù)據(jù)類型的實例,只能在數(shù)據(jù)類型的場景(context)中使用。
2變成數(shù)據(jù)類型以后,原來的函數(shù)就不能用了。因為"(+3)"這個函數(shù)是處理值的(簡稱"值函數(shù)"),而不是處理數(shù)據(jù)類型的。
我們需要重新定義一種運(yùn)算。它接受"值函數(shù)"和數(shù)據(jù)類型的實例作為輸入?yún)?shù),使用"值函數(shù)"處理后,再輸出數(shù)據(jù)類型的另一個實例。上圖的fmap就代表了這種運(yùn)算。
fmap的內(nèi)部,實際上是這樣:打開封裝的數(shù)據(jù)類型,取出值,用值函數(shù)處理以后,再封裝回數(shù)據(jù)類型。
一個有趣的問題來了。如果我們把函數(shù)也封裝成數(shù)據(jù)類型,會怎樣?
上圖就是把函數(shù)"(+3)"封裝成一種數(shù)據(jù)類型。
這時,就需要再定義一種新的運(yùn)算。它不是值與值的運(yùn)算,也不是值與數(shù)據(jù)類型的運(yùn)算,而是數(shù)據(jù)類型與數(shù)據(jù)類型的運(yùn)算。
上圖中,兩個數(shù)據(jù)類型進(jìn)行運(yùn)算。首先,取出它們各自的值,一個是函數(shù),一個是數(shù)值;然后,使用函數(shù)處理數(shù)值;最后,將函數(shù)的返回結(jié)果再封裝進(jìn)數(shù)據(jù)類型。
函數(shù)可以返回值,當(dāng)然也可以返回數(shù)據(jù)類型。
我們需要的是這樣一種函數(shù):它的輸入和輸出都是數(shù)據(jù)類型。
這樣做的好處是什么?
因為數(shù)據(jù)類型是帶有運(yùn)算方法的,如果每一步返回的都是數(shù)據(jù)類型的實例,我們就可以把它們連接起來。
來看一個實例,系統(tǒng)的I/O提供了用戶的輸入。
getLine函數(shù)可以將用戶的輸入處理成一個字符串類型(STR)的實例。
readfile函數(shù)接受STR實例當(dāng)作文件名,返回一個文件類型的實例。
putStrLn函數(shù)將文件內(nèi)容輸出。
所有這些運(yùn)算連起來,就叫做Monad。
簡單說,Monad就是一種設(shè)計模式,表示將一個運(yùn)算過程,通過函數(shù)拆解成互相連接的多個步驟。你只要提供下一步運(yùn)算所需的函數(shù),整個運(yùn)算就會自動進(jìn)行下去。
更多建議: