天天看點

Scalaz(18)- Monad: ReaderWriterState-可以是一種簡單的程式設計語言

 說道fp,我們馬上會聯想到monad。我們說過monad的代表函數flatmap可以把兩個運算f[a],f[b]連續起來,這樣就可以從程式的意義上形成一種串型的流程(workflow)。更直白的講法是:任何類型隻要實作了flatmap就可以用for-comprehension, for{...}yield。在這個for{...}裡我們可以好像oop一樣編寫程式。這個for就是一種運算模式,它規範了在for{...}裡指令的行為。我們正從oop風格走入fp程式設計模式,希望有個最基本的fp程式設計模式使我們能夠沿用oop程式設計風格的文法和思維。monad應該就是最合适的泛函資料類型了。我們先從最基本的開始:假如我們有一段行令程式:

通過這些函數e1,e2,e3最後計算出d值。如果是用fp風格來編這段程式的話,首先我們必須把函數的結果d放入f[d]的f裡。f就是上面所說的運算模式,在這裡可以用大家熟悉的context(上下文)來表示。f必須是個monad,f[]相當于for{...}yield。我們先試試用id,雖然id[a]對a不做任何處理,直接傳回,好像沒什麼意義,但這種類型具備了map和flatmap,應該可以用for-comprehension:

可以看到,在for-loop裡就是oop的行令程式。不過如果覺着這個id沒什麼意義,可以試試option看:

看,雖然換了個殼子(context), 但for-loop裡的程式沒有變化。換一句話講就是for-loop裡的程式根本不理會包裹的context。

reader也是一種monad,用它又怎樣呢:

雖然在文法上有些蹩腳,但還是證明了for-loop裡的程式是不理會外面context的。那麼我們可不可以說這個prg就是一個簡單的fp程式設計語言。它把運算結果放在context裡,直至運作了某種interpreter才能取得實際的運算值(用run(10)得到22)。當然,一段程式,它的運算行為受制于單一種類型的context可能有些弱了。如果需要獲得一種可用的fp程式設計語言,我們可能還是要探讨如何把單一類型context組合成多類型混合的context。

我們發現在scalaz裡有些type class的名稱是以t結束的如:readert,writert,statet等等。這個t指的是變形器transformer,意思是用它可以堆砌(stacking)context。看看statet,簡單定義應該是這樣的: 

我們可以把f類堆砌在state上。實踐證明如果這個f實作了flatmap,那麼堆砌成的類型也能實作flatmap。好,scalaz的option是實作了flatmap的,那麼能不能把它和state堆砌在一起呢?堆砌而成的context會有什麼效果呢?我們先看看單一option和state作為一種context的效果:

依我來看,option主要效果是在遇到none值時立即退出。而state的主要作用是在運算同時可以維護一個狀态。那麼如果把option和state疊加起來就會同時具備這兩種類型的特點了吧?也就是既能維護狀态又能在遇到none值時立即終止運算退出了。首先驗證一下用option的flatmap來實作疊加context的flatmap:

是的,我們可以用option的map和flatmap來實作optionstate的map和flatmap。當然,如果我們想在一個for-comprehension裡同時使用option和state就必須把它們升格成optionstate類型:

現在試試用疊加效果的for-comprehension:

看,既可以維護狀态又具備none處理機制。

好了,scalaz裡有個readerwriterstate這麼個type class,就是一個reader+writer+state堆砌的monad。相信scalaz特别提供了這麼個type class應該有它的用意。我的猜想是這個monad是個功能比較完整的組合monad。作為for-comprehension的context應該能提供比較全面的效果。從字意上解釋就是在由它形成的monadic程式設計語言裡可以同時提供運算(compute)、跟蹤(logging)和狀态維護功能。它的基礎類型是indexedreaderwriterstatet:scalaz/package.scala

如果把reader,writer,state款式分開來對比分析的話:

那麼把以上三個結合起來後它的款式應該是這樣的了吧:

傳入的和傳回的類型是比對的。在scalaz裡是這樣定義的:scalaz/readerwriterstatet.scala

我們看到indexedreaderwriterstatet已經實作了很多indexedstatet的運算方法如:eval,exec等。看看它的map和flatmap是怎麼實作的:

與我們前面所做的optionstate例子一樣:如果f能實作map和flatmap則indexedreaderwriterstatet就能實作map和flatmap。為了省卻在for-loop裡每行指令都使用lift進行類型升格,indexedreaderwriterstatet重新實作了大部分操作函數:

我們示範用這個readerwriterstate來寫一段程式:模拟一段通訊端口使用程式并把使用情況記錄下來。先傳入一個端口号,在程式中可以重設使用的端口号:

這倒像是一段進階語言寫的程式。細節都在幾個功能函數裡。它們都必須傳回readerwriterstate類型:

繼續閱讀