標籤:

Haskell入門瓶頸(二)

好了,洗澡歸來。

什麼是do,什麼是<-。說實話do倒無所謂,第一次看到<-我是想罵娘的。有向右的箭頭竟然還有向左的。

這兩個東西其實構成了一個語法糖。而這個語法糖可以應用在所有monad上。ok,一般解釋一個新的東西應該把它聯繫到一個已知的東西。do和<-被聯繫到了monad,但monad又是什麼鬼。我們先放下do和<-,先回答這個問題,啥是monad?

啥是monad呢?直接看monad的定義是很奇怪的,先看看monad實現了什麼。下面假定讀者對functor有一定的了解。

首先,monad一定是一個functor。Haskell中的List,List實現了fmap函數之後。我們可以這樣

fmap (x -> x + 1) [1,2,3] -- 這樣會得到[2,3,4]

上面是一般的情況,只是想讓List的每個元素加1。有時候我們可能之前寫了一些其他函數,比如像下面這樣

tripleElement :: a -> [a]tripleElement x = [x, x, x] -- 複製三分原來的元素。-- 你可能需要吧一個List中的所有元素都複製成三分,使用fmapfmap tripleElement [1, 2, 3] -- 得到[[1, 1, 1],[2, 2, 2],[3, 3, 3]]

上面這樣就變成了2層的List,處理這些數據的時候就比較繁瑣了。於是乎這時候就該使用monad了,其實monad就是來拆包的[[1, 1, 1],[2, 2, 2],[3, 3, 3]]這樣的兩層包太丑了。好接下來引入另一個神奇的符號>>=(這其實就是個中綴函數)。

functor就是實現了fmap函數的一個代數結構,如果再實現一下>>=那他就可以變成monad。而實現這玩意就是給出一個如果出現上面2層包的結構的時候,如何解包。

(>>=) :: m a -> (a -> m b) -> m b -- 這是>>=的類型-- 當你需要手動實現List讓List變成monad的時候你得寫一下這個List的>>=instance Monad [] where xs >>= f = foldl (++) [] (fmap f xs)-- 這樣實現之後你就可以這樣了[1, 2, 3] >>= tripleElement -- 得到[1,1,1,2,2,2,3,3,3]

所以簡單的理解,monad就是給functor提供了拆包的能力。不會一直累積太多的層次。

maybe的例子。// todo

ok,monad大致就這可以先這麼簡單的理解,然後是do和<-。

-- 程序一main = do print "Hello World" print "Hello dlroW"-- 程序二main = do userInput <- getLine print userInput

之前寫到這兩個程序,先分析一下第一個程序。第一個程序用了一個do最為開始,然後連續寫了兩個print。首先需要注意的是,main的類型是一個IO,而IO也是monad的實例,所以他可以與整個do表達式劃等號。並且Haskell會自動理解這邊的do會構造成一個IO。其實這邊整個的do表達式可以改寫成下面這樣

main = (print "Hello World") >> (print "Hello dlroW") -- 上面的程序一其實就是這種形式寫法的語法糖,>>與>>=類似,-- 區別在於>>不需要傳入一個函數,只要傳入另一個monad就可以了-- 實際上這邊也可以使用>>=main = (print "Hello World") >>= (x -> print "Hello dlroW") -- 注意這邊的x沒啥用,當x沒啥用的時候直接用>>就可以了

程序二就不一樣了,需要使用>>=

-- 程序二就是下面代碼的語法糖main = getLine >>= (userInput -> print userInput)-- pointfree寫法main = getLine >>= print

分析一下為啥程序一用>>就行了,二卻需要>>=。首先程序一種的print "Hello World"的類型是IO (),()這個東西其實代表返回值沒啥影響。而getLine的類型是IO String,String是用戶的輸入,我們需要拿到這個輸入並且處理它,這個monad攜帶的值對於這個程序是有意義的所以要用>>=。

最後就是<-這個符號了,<-表示把monad裡面的只拿出來,對應>>=。程序二中的getLine是一個IO String,userInput <- getLine 就是把裡面的String拿出來了。

總結:這邊這個do不能理解成構建了一個代碼塊,而是monad的語法糖。按照代碼塊理解的話會很坑。這邊的IO其實是一個非同步的,且帶有副作用的monad。為啥子IO要整成一個monad,有啥子好處?這個暫時我還沒有答案,但是用著挺好用的。

推薦閱讀:

Haskell的遞歸
fp101公開課中Functional Parsers - Part 2的這個代碼是正確的么?
使用 parsec 處理左遞歸
GHC擴展-XRankNTypes是什麼?如何理解forall .?

TAG:Haskell |