泛函編程(16)-泛函狀態-Functional State
初接觸泛函狀態覺著很不習慣。主要是在使用State數據類型時很難理解其中的原理,特別是泛函狀態變遷機制(state transition mechanism):怎麽狀態就起了變化,實在難以跟蹤。我想這主要是因為狀態變遷機制經過了函數組合,已經深深的埋藏在運行代碼後面。上節我們討論到RNG,對於了解State類型是個很好的開頭。RNG簡單描述了泛函方式的狀態變遷及支持狀態變遷所需要的數據結構和操作函數款式。
在上節我們提到過 type Rand[+A] = RNG => (A, RNG),Rand是一個隨意數產生函數。由於Rand是個類型,一個函數類型,所以可以被當作參數或者返回值來使用。我們把這個定義再擴展一下,變得更通用一些:type State[S, +A] = S => (A, S)。Rand就是State的一個特殊案例:type Rand[+A] = State[RNG, +A] 。我們稱State為狀態行為,即S => (A,S)是一個定義狀態變遷方式的函數。State類型的狀態變遷機制就是通過狀態行為函數來確定的。再次聚焦一下我們設計State類型的目標:State類型不但可以使我們像設計其它類型一樣封裝一個較低階類型元素並且提供一套狀態變遷機制,而且狀態變遷機制是泛函式的,自然隱性的。
我們先試試簡單的State類型設計:
1 case class State[S,+A](run: S => (A, S))
沒錯,就是這麽簡單,也是我刻意為之。註意狀態行為函數run是State類的內部成員,我們有針對性的把一個State的狀態變遷機制通過在構建State類時作為參數註入。然後產生的State實例就會按照我們期待的那樣進行狀態變遷了。case class自備了apply,這樣我們可以直接使用State(???)創建State實例。我會把State(s => (a,s))寫成State { s => (a,s)},這樣表達傳入的是一段代碼會更形象自然一點。State[]既然是一個高階類型,那麽我們應該也為它提供一套在管子內部進行元素操作的函數。切記!切記!在處理管子內封裝元素值的同時要按照狀態行為函數的要求對類型狀態進行相應變遷。
先從高階類型最基本的組件開始:
1 object State { 2 def unit[S,A](a: A) = State[S,A](s => (a, s)) 3 }
我們前面接觸過這個unit。它就是一個封裝元素值和狀態都不轉變的State實例。unit的唯一功能就是把低階一級的封裝元素類型a升格為State類型。
我們來編寫一個State函數,切記!切記!要同時處理狀態變遷機制:
1 case class State[S,+A](run: S => (A, S)) { 2 def flatMap[B](f: A => State[S,B]): State[S,B] = State[S,B] { 3 s => { 4 val (a, s1) = run(s) 5 f(a).run(s1) 6 } 7 }
在flatMap裏我們用函數f處理了封裝元素a, f(a)。同時我們又引用了狀態行為函數run對傳入的狀態s進行了狀態變遷 run(s)。
1 def map[B](f: A => B): State[S,B] = State[S,B] { 2 s => { 3 val (a, s1) = run(s) 4 (f(a),s1) 5 } 6 } 7 def map_1[B](f: A => B): State[S,B] = { 8 flatMap { a => unit(f(a)) } 9 }
同樣,map也實施了f(a),run(s)。map也可以用flatMap來實現。它們之間的分別只是f: A => B 和 A => State[S,B]。因為我們有unit, unit(a) = State[S,A],unit(f(a)) = State[S,B]所以我們用unit把map的函數參數A升格就行了。用flatMap來實現map可以把map抽升到更高級:這樣map就不用再理會那個狀態行為函數了。
那麽map2呢?
1 def map2[B,C](sb: State[S,B])(f: (A,B) => C): State[S,C] = { 2 flatMap {a => sb.map { b => f(a,b) }} 3 } 4 def map3[B,C,D](sb: State[S,B], sc: State[S,C])(f: (A,B,C) => D): State[S,D] = { 5 flatMap {a => sb.flatMap {b => sc.map { c => f(a,b,c) }}} 6 }
map2的功能是用封裝元素類型函數(A,B) => C來把兩個State管子裏的元素結合起來。我們可以施用flatMap兩次來把兩個管子裏的元素結合起來。對於map3我們可以再加一次。
另一種連續施用flatMap的表達方式:
1 def map2_1[B,C](sb: State[S,B])(f: (A,B) => C): State[S,C] ={ 2 for { 3 a <- this 4 b <- sb 5 } yield f(a,b) 6 } 7 def map3_1[B,C,D](sb: State[S,B], sc: State[S,C])(f: (A,B,C) => D): State[S,D] ={ 8 for { 9 a <- this 10 b <- sb 11 c <- sc 12 } yield f(a,b,c) 13 }
以上的語法糖(syntatic sugar)for-comprehension讓我們儼然進入了一個泛函世界,好像有了一種興奮的感覺。這種表達形式簡潔直白,更加容易理解。同樣,在map2,map3裏沒有涉及到任何狀態變遷的東西。我們實現了狀態變遷的隱形操作。
下面舉個切實例子來示範泛函狀態:
1 //Stack類型就是一個List[Int],後面比較容易表達點 2 type Stack = List[Int] 3 //pop就是一個State實例。它的狀態行為函數是partial function:把一個現成的List[Int]拆分成新的值和狀態 4 //即把第一個元素去掉放到值裏 5 def pop = State[Stack, Int]{ case x::xs => (x, xs) } 6 //> pop: => ch6.state.State[ch6.state.Stack,Int] 7 //push就是一個State實例。它的狀態行為函數把i壓到一個現成的List[Int]上,跟值沒有任何關系 8 def push(i: Int) = State[Stack, Unit]{ case xs => ((), i :: xs ) } 9 //> push: (i: Int)ch6.state.State[ch6.state.Stack,Unit] 10 def stackRun: State[Stack, Int] = { 11 for { 12 _ <- push(13) 13 a <- pop 14 b <- pop 15 } yield a+b 16 } //> stackRun: => ch6.state.State[ch6.state.Stack,Int] 17 18 val (a, s) =stackRun.run(List(10,11,12)) //> a : Int = 23 19 //| s : ch6.state.Stack = List(11, 12)
在stackRun裏我們沒有在任何地方提到狀態Stack,但看看運行結果(a,s):不但返回值是正確的,而且Stack狀態也默默地發生了轉變。如果嘗試從stackRun的代碼裏去分析狀態是如何轉變的是永遠無法理解的,建議還是老老實實從頭來吧。
泛函狀態是一種隱形自動的變遷,那麽如果我們需要打亂既定流程,手動設定或者臨時讀取狀態時該怎麽辦呢?
1 object State { 2 def unit[S,A](a: A) = State[S,A](s => (a, s)) 3 def getState[S]: State[S,S] = State[S,S] { s => (s,s) } 4 def setState[S](s: S): State[S,Unit] = State[S,Unit] { _ => ((),s)} 5 6 }
還是通過狀態行為函數來實現的。
1 def stackRun: State[Stack, Int] = { 2 for { 3 _ <- push(13) 4 a <- pop 5 _ <- setState(List(8,9)) 6 b <- pop 7 s1 <- getState 8 } yield (a + b) 9 } //> stackRun: => ch6.state.State[ch6.state.Stack,Int] 10 11 val (a, s) =stackRun.run(List(10,11,12)) //> a : Int = 21 12 //| s : ch6.state.Stack = List(9)
我們可以臨時將狀態設置成List(8,9)。
泛函編程(16)-泛函狀態-Functional State