使用Scala對帶狀態函式或API進行抽象的示例(State)
阿新 • • 發佈:2019-01-25
這個例子來源於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實現一個純函式風格的引用透明的偽隨機數生成器》