1. 程式人生 > >使用Scala對帶狀態函式或API進行抽象的示例(State)

使用Scala對帶狀態函式或API進行抽象的示例(State)

這個例子來源於scala聖經級教程《Functional Programming in Scala》,由於本人跟著書中的程式碼敲了一遍,然後寫了點測試程式碼驗證了一下正確性,所以就放在這做個備忘吧。貼出來只是為了方便自己,如果看不懂,但是又感興趣的就去看原書吧……

package state
import RNG.Simple
import state.State._

case class State[S, +A](run: S => (A, S)) {

  def map[B](f: A => B): State[S, B] = flatMap(a => unit(f(a)))


  def
flatMap[B](f: A => State[S, B]):
State[S, B] = State(s => { val (a, s1) = run(s) f(a).run(s1) }) def map2[B, C](sb: State[S, B])(f: (A, B) => C): State[S, C] = flatMap(a => sb.map(b => f(a, b))) } object State { type Rand[A] = State[RNG, A] def unit[S, A](a: A):
State[S, A] = State(s => (a, s)) def get[S]: State[S, S] = State(s => (s, s)) def set[S](s: S): State[S, Unit] = State(_ => ((), s)) def modify[S](f: S => S): State[S, Unit] = for { s <- get _ <- set(f(s)) } yield () // The idiomatic solution is expressed via foldRight def
sequenceViaFoldRight[S, A](sas: List[State[S, A]]):
State[S, List[A]] = sas.foldRight(unit[S, List[A]](List()))((f, acc) => f.map2(acc)(_ :: _)) def sequence[S, A](sas: List[State[S, A]]): State[S, List[A]] = { def go(s: S, actions: List[State[S,A]], acc: List[A]): (List[A], S) = actions match { case Nil => (acc.reverse, s) case h :: t => h.run(s) match { case (a,s2) => go(s2, t, a :: acc) } } State((s: S) => go(s, sas, List())) } def sequenceViaFoldLeft[S,A](l: List[State[S, A]]): State[S, List[A]] = l.reverse.foldLeft(unit[S, List[A]](List()))((acc, f) => f.map2(acc)( _ :: _ )) def main(args: Array[String]): Unit = { val simple = Simple(System.currentTimeMillis()) println(simple) println(State.unit[RNG, Int](5).run(simple)) val li5Ele = List.fill(5)(State.unit[RNG, Int](5)) val sequenceFun = State.sequence(li5Ele) val sequenceVal = State.sequence(li5Ele).run(simple) println(sequenceVal) val li6Ele = List.fill(6)(State(RNG.int)) val sequence6EleVal = State.sequence(li6Ele).run(simple) println(sequence6EleVal) val sequenceViaFoldRightVal = State.sequenceViaFoldRight(li6Ele).run(simple) println(sequenceViaFoldRightVal) val sequenceViaFoldLeftVal = State.sequenceViaFoldLeft(li6Ele).run(simple) println(sequenceViaFoldLeftVal) println(State.set(simple).run(simple)._1) println(State.set(simple).run(simple)._2) println("---------------------------------------------------------------") println(State.get.run(simple)._1) println(State.get.run(simple)._2) println("---------------------------------------------------------------") println(State.get[RNG].map[Int](_.nextInt._1).run(simple)) println(State.get[RNG].map[RNG](_.nextInt._2).run(simple)) //對傳入的simple物件呼叫一次nextInt方法,從而轉換為另一個simple物件 println(State.set(simple.nextInt._2).run(simple)) //對傳入的simple物件呼叫一次nextInt方法,從而轉換為另一個simple物件 val modifyVal =State.modify((a: RNG) => a.nextInt._2).run(simple) println(modifyVal) /////////////////////////////////////////////////////////////////////////////////////// /** * 模擬交通燈顏色的變化 * @param a * @return */ def trafficLight(a: String): (String, String) = a match { case "紅" => ("紅", "綠") case "綠" => ("綠", "黃") case "黃" => ("黃", "紅") case _ => ("紅", "綠") } val li5 = List.fill(10)(State((a: String) => trafficLight(a))) val redStart = State.sequence[String, String](li5).run("紅") println(redStart) val greenStart = State.sequence[String, String](li5).run("綠") println(greenStart) val xStart = State.sequence[String, String](li5).run("xxx") println(xStart) /////////////////////////////////////////////////////////////////////////////////////// val machine = Machine(true, 4, 0) val inputs = List(Coin, Turn, Coin, Turn, Coin, Turn, Coin, Turn, Coin, Turn, Coin, Turn) val machineRes = Candy.simulateMachine(inputs).run(machine) println(machineRes) val _machineRes = Candy._simulateMachine((inputs)).run(machine) println(_machineRes) } } sealed trait Input /** * 表示一枚硬幣 */ case object Coin extends Input /** * 表示糖果售貨機的按鈕 */ case object Turn extends Input /** * 表示糖果售貨機:機器有2種輸入方式,可以投入硬幣,也可以按動按鈕獲取糖果 * 機器有2種狀態:鎖定狀態和非鎖定狀態 */ case class Machine(locked: Boolean, candies: Int, coins: Int) object Candy { /** * 對鎖定的售貨機投入硬幣,如果有剩餘的糖果它將變為非鎖定狀態 * 對非鎖定狀態的售貨機按下按鈕,它將給出糖果並變回鎖定狀態 * 對鎖定狀態的售貨機按下按鈕,或對非鎖定狀態的售貨機投入硬幣,什麼也不做 */ def update = (i: Input) => (s: Machine) => (i, s) match { case (_, Machine(_, 0, _)) => s case (Coin, Machine(false, _, _)) => s case (Turn, Machine(true, _, _)) => s case (Coin, Machine(true, candy, coin)) => Machine(false, candy, coin + 1) case (Turn, Machine(false, candy, coin)) => Machine(true, candy - 1, coin) } /** * 對一個糖果售貨機建模的有限狀態機 */ def simulateMachine(inputs: List[Input]): State[Machine, (Int, Int)] = for { _ <- sequence(inputs map (modify[Machine] _ compose update)) s <- get } yield (s.coins, s.candies) /** * _simulateMachine is equal to simulateMachine, but not so brief * _simulateMachine contains debug statements */ def _simulateMachine(inputs: List[Input]): State[Machine, (Int, Int)] = { val machines = for { ele <- inputs } yield modify[Machine]({ print(ele); print(" "); update(ele)}) // print(ele); print(" ") for debug purpose for { _ <- sequence(machines) s <- get } yield (s.coins, s.candies) } }

上述程式碼的執行結果是:

Simple(1530871197922)
(5,Simple(1530871197922))
(List(5, 5, 5, 5, 5),Simple(1530871197922))
(List(760619510, 1890169447, -624214092, 759529940, -1356984262, -171369807),Simple(270244085079856))
(List(760619510, 1890169447, -624214092, 759529940, -1356984262, -171369807),Simple(270244085079856))
(List(760619510, 1890169447, -624214092, 759529940, -1356984262, -171369807),Simple(270244085079856))
()
Simple(1530871197922)
---------------------------------------------------------------
Simple(1530871197922)
Simple(1530871197922)
---------------------------------------------------------------
(760619510,Simple(1530871197922))
(Simple(49847960230981),Simple(1530871197922))
((),Simple(49847960230981))
((),Simple(49847960230981))
(List(紅, 綠, 黃, 紅, 綠, 黃, 紅, 綠, 黃, 紅),綠)
(List(綠, 黃, 紅, 綠, 黃, 紅, 綠, 黃, 紅, 綠),黃)
(List(紅, 綠, 黃, 紅, 綠, 黃, 紅, 綠, 黃, 紅),綠)
((4,0),Machine(true,0,4))
Coin  Turn  Coin  Turn  Coin  Turn  Coin  Turn  Coin  Turn  Coin  Turn  ((4,0),Machine(true,0,4))

注:

程式碼中的RNG參見本人同類文章《用Scala實現一個純函式風格的引用透明的偽隨機數生成器》