# 泛函编程（17）－泛函状态－State In Action

对OOP编程人员来说，泛函状态State是一种全新的数据类型。我们在上节做了些介绍，在这节我们讨论一下State类型的应用：用一个具体的例子来示范如何使用State类型。以下是这个例子的具体描述：

1、如果机内有糖的话，投入硬币贩售机从锁定状态进入放开状态

2、在放开状态下扭动旋钮贩售机放出一块糖果后自动进入锁定状态

3、在锁定状态下扭动旋钮贩售机不做反应

4、在放开状态下投入硬币贩售机不做反应

5、没有糖果的贩售机对任何操作都不做反应

`1 type candy = Int //方便表达2 type coin = Int //方便表达3  sealed trait Input4 case object Coin extends Input //投币5 case object Turn extends Input //旋钮6 case class Machine(locked: Boolean, candies: candy, coins: coin) //状态类`

Machine类型就是需要变迁的状态，状态内容包括锁定状态locked,当前糖果数candies,当前硬币数coins。

`1 def simulateMachine(inputs: List[Input]): State[Machine,coin]`

`1 def transition(input: Input, machine: Machine): Machine = {2  (input, machine) match {3 case (_, Machine(_,0,_)) => machine4 case (Turn, Machine(true,_,_)) => machine5 case (Coin, Machine(false,_,_)) => machine6 case (Coin, Machine(true, _, nCoin)) => machine.copy(locked = false, coins = nCoin + 1)7 case (Turn, Machine(false, nCandy, _)) => machine.copy(locked = true, candies = nCandy - 1)8  }9 }`

transition只能处理单个操作动作，那么如果我们输入一个List的操作动作该如何连续处理呢？既然涉及List,自然想到用递归算法应该能行：

`1 def execute(inputs: List[Input], machine: Machine): Machine = {2  inputs match {3 case Nil => machine4 case h::t => execute(t, transition(h,machine))56  }7 }`

`1 for {2 s0 <- getState //读取起始状态3 _ <- setState(execute(inputs,s0)) //人工设定新状态4 s1 <- getState //读取当前状态5 } yield s1.coins`

` 1 type candy = Int //方便表达 2 type coin = Int //方便表达 3  sealed trait Input 4 case object Coin extends Input 5 case object Turn extends Input 6 case class Machine(locked: Boolean, candies: candy, coins: coin) 7 8 def simulateMachine(inputs: List[Input]): State[Machine,coin] = { 9 def transition(input: Input, machine: Machine): Machine = {10  (input, machine) match {11 case (_, Machine(_,0,_)) => machine12 case (Turn, Machine(true,_,_)) => machine13 case (Coin, Machine(false,_,_)) => machine14 case (Coin, Machine(true, _, nCoin)) => machine.copy(locked = false, coins = nCoin + 1)15 case (Turn, Machine(false, nCandy, _)) => machine.copy(locked = true, candies = nCandy - 1)16  }17  }18 def execute(inputs: List[Input], machine: Machine): Machine = {19  inputs match {20 case Nil => machine21 case h::t => execute(t, transition(h,machine))2223  }24  }25 for {26 s0 <- getState //读取起始状态27 _ <- setState(execute(inputs,s0)) //人工设定新状态28 s1 <- getState //读取当前状态29  } yield s1.coins30 } //> simulateMachine: (inputs: List[ch6.state.Input])ch6.state.State[ch6.state.M31 //| achine,ch6.state.coin]3233 val inputs = List(Coin, Turn, Coin, Turn, Turn, Coin, Coin, Coin, Turn)34 //> inputs : List[Product with Serializable with ch6.state.Input] = List(Coin,35 //| Turn, Coin, Turn, Turn, Coin, Coin, Coin, Turn)36 simulateMachine(inputs).run(Machine(true,3,0)) //> res0: (ch6.state.coin, ch6.state.Machine) = (3,Machine(true,0,3))37  `

` 1 def modify[S](f: S => S): State[S,Unit] = { 2 for { 3 s0 <- getState 4 _ <- setState(f(s0)) 5  } yield () 6  } 7 def sequence[S,A](xs: List[State[S,A]]): State[S,List[A]] = { 8 def go(s: S, actList: List[State[S,A]], acc: List[A]): (List[A],S) = { 9  actList match {10 case Nil => (acc.reverse, s) //纠正排序11 case h::t => h.run(s) match {case (a2,s2) => go(s2,t,a2 :: acc) }12  }13  }14 State(s => go(s,xs,List()))15 }`

modify比较直白：取出当前状态，对这个状态进行变迁后再设成当前状态。sequence稍微复杂一点。我们先从它的类型匹配开始分析：接收一个List[State]、输出State[List]，换句话说就是把一连串的状态变成一个状态内的一连串值。这不刚好和我们模拟函数要求匹配吗？我们要求一个函数对一连串的操作动作进行处理后产生一个最终的状态。sequence函数内部已经包含了处理循环，我们不需要execute函数了。但是这个版本的sequence函数比较低级：我是指它使用了递归算法，必须在函数内部实现状态行为run(s)。如果我们用高价一点的函数实现sequence，有可能不需要理会run(s)了：

`1 //用右折叠：输入与输出同排序，但不是tail recursive2 def sequenceByRight[S,A](xs: List[State[S,A]]): State[S,List[A]] = {3 xs.foldRight(unit[S,List[A]](List())){ (f,acc) => f.map2(acc)(_ :: _) }4  }5 //用左折叠：输入与输出反排序，是tail recursive6 def sequenceByLeft[S,A](l: List[State[S, A]]): State[S, List[A]] = {7 l.reverse.foldLeft(unit[S, List[A]](List()))((acc, f) => f.map2(acc)( _ :: _ ))8 }`

` 1 def simulateMachineFP(inputs: List[Input]): State[Machine,coin] = { 2 for { 3 _ <- sequence{ 4  inputs.map { 5 input => modify { 6 machine: Machine => { 7  (input, machine) match { 8 case (_, Machine(_,0,_)) => machine 9 case (Turn, Machine(true,_,_)) => machine10 case (Coin, Machine(false,_,_)) => machine11 case (Coin, Machine(true, nCandy, nCoin)) => Machine(false,nCandy, nCoin+1)12 case (Turn, Machine(false, nCandy, nCoin)) => Machine(true, nCandy - 1, nCoin)13  }14  }15  }16  }17  }18 s <- getState19  } yield s.coins20 } //> simulateMachineFP: (inputs: List[ch6.state.Input])ch6.state.State[ch6.state21 //| .Machine,ch6.state.coin]22 simulateMachineFP(inputs).run(Machine(true,3,0)) //> res1: (ch6.state.coin, ch6.state.Machine) = (3,Machine(true,0,3))`

_ <- sequence ...modify 起到了 _ <- setState 的作用，所以我们可以用 s <- getState 把最新的状态读出来。

后面补充一下：如果我来选择，我会稍退一步；把逻辑部分提出来：

` 1 def simulateMachineConcise(inputs: List[Input]): State[Machine,coin] = { 2 def transition(input: Input, machine: Machine): Machine = { 3  (input, machine) match { 4 case (_, Machine(_,0,_)) => machine 5 case (Turn, Machine(true,_,_)) => machine 6 case (Coin, Machine(false,_,_)) => machine 7 case (Coin, Machine(true, nCandy, nCoin)) => Machine(false,nCandy, nCoin+1) 8 case (Turn, Machine(false, nCandy, nCoin)) => Machine(true, nCandy - 1, nCoin) 9  }10  }1112  for {13 _ <- sequence{inputs.map {input => modify {machine: Machine => transition(input, machine)}}} s <- getState15  } yield s.coins16 } //> simulateMachineConcise: (inputs: List[ch6.state.Input])ch6.state.State[ch6.17 //| state.Machine,ch6.state.coin]18 simulateMachineConcise(inputs).run(Machine(true,3,0))19 //> res2: (ch6.state.coin, ch6.state.Machine) = (3,Machine(true,0,3))20  `

`1 for {2 _ <- sequence{inputs.map {input => modify {machine: Machine => transition(input, machine)}}} s <- getState3 } yield s.coins`

Top