使用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实现一个纯函数风格的引用透明的伪随机数生成器》

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值