scala函数式编程笔记: 纯函数式状态

scala函数式编程:纯函数式状态读书笔记

Overview:

  • 带状态的方法的声明式实现可能带有副作用,难以保持引用透明。
  • 以纯函数式的方式实现带状态的函数的关键在于让状态更新是显式的,不要以副作用方式更新状态,而是连同生成的值一起返回一个新的状态。即把状态的隐式更改暴露出去。
  • 例:函数nextInt返回改变后的新状态nextRNG
trait RNG{
  def nextInt: (Int,RNG)
}

case class SimpleRNG(seed: Long) extends RNG{
  //返回一个随机Int值,纯函数式实现,返回新的SimpleRNG
  def nextInt: (Int,RNG) = {
    val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFL
    val nextRNG = SimpleRNG(newSeed)
    val n = (newSeed >>> 16).toInt

    (n, nextRNG)
  }
  
  //带有副作用的实现,currentSeed的更新是隐含的
  val currentSeed = seed
  def nextInt2: Int = {
    val newSeed = (currentTime * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFL
    currentSeed = newSeed
    val n = (newSeed >>> 16).toInt

    n
  }
}

组合子:

  • 组合子是一个高阶函数,可以看作函数的类型别名,用来避免显示地进行状态传递
  • RNG=>(A,RNG)是一个函数,接受旧RNG,得到新的RNG,状态行为可以通过组合子来表示,如组合子Rand:
type Rand[+A] = RNG => (A, RNG)
  • 实例分析:
    • val int: Rand[Int] = _.nextInt
    • _.nextInt 等价于 RNG => (Int,RNG)。即接受一个RNG类型参数并返回一个(Int,RNG)类型元组的函数。
    • Rand[Int]是函数RNG => (Int,RNG)的类型别名。

使用组合子来组合状态行为:

  • 即对于有状态的函数,首先将其参数和返回值包装为组合子。然后通过map,flatMap等来以纯函数的形式实现该函数。其中f,g等即该函数。
  • 一个状态行为: s(rng1: RNG)返回(v: Int,rng2: RNG)
def map[A,B](s: Rand[A])(f: A => B): Rand[B] = 
    rng1 => {
        val (v,rng2) = s(rng1)
        (f(v),rng2)
    }
    
def flatMap[A,B](f: Rand[A)(g: A => Rand[B]): Rand[B] = 
    rng => {
        (v,r) = f(rng)
        g(v)(r)
    }
  • 组合2个状态行为:
def map2[A,B,C](ra: Rand[A], rb: Rand[B])(f: (A,B) => C): Rand[c] = 
    rng1 => {
        val (v1,rng2) = ra(rng1)
        val (v2,rng3) = rb(rng2)
        (f(v1,v2),rng3)
    }
  • 组合一序列状态行为:
def unit[A](a: A): Rand[A] = rng => (a,rng)

def sequence[A](fs: List[Rand[A]]): Rand[List[A]] =
    List.foldRight(fs,unit(List[A())))((x,y) => map2(x,y)((a,b) => Cons(a,b)))

通用的状态行为数据类型

  • 前边组合子状态是类型RNG。我们来把组合子泛化。S表示任何状态。
type State[+A,S] = S => (A,S)
  • 我们也可以将泛化的组合子定义为一个class。State[+A,S]中定义的函数是实现函数式风格的任何状态机或带状态程序所需的全部工具。
case class State[+A,S](run: S => (A,S)) extends AnyVal{
  
  def map[B](f: A => B): State[B,S] = State(
    s => {
      val (i,r) = run(s)
      (f(i),r)
    })

  def map2[B,C](b: State[B,S])(f: (A,B) => C): State[C,S] = State(
    s => {
      val (v1,s1) = run(s)
      val (v2,s2) = b.run(s1)
      (f(v1,v2),s2)
    })

  //def _map2[B,C](b: State[B,S])(f: (A,B) => C): State[C,S] =
  //  flatMap(x => b.map(y => f((x,y))))


  def flatMap[B](f: A => State[B,S]): State[B,S] = State(
    s => {
      val (v,s1) = run(s)
      f(v).run(s1)
    })
}

object State{

  def unit[A,S](x: A): State[A,S] = State(s => (x,s))
  def sequence[A,S](ls: List[State[A,S]]): State[List[A],S] =
    List.foldRight(ls,unit[List[A],S](List()))((x,y) => x.map2(y)(Cons(_,_)))

  def modify[S](f: S => S): State[Unit,S] = for {
    s <- get  //获取当前状态分配给s
    _ <- set(f(s)) //设置新状态
  } yield ()

  def get[S]: State[S,S] = State(s => (s,s))
  def set[S](s: S): State[Unit,S] = State(_ => ((), s))
}

应用实例

  • 题目

EXERCISE 13 (hard): To gain experience with the use of State, implement a simulation of a simple candy dispenser. The machine has two types of input: You can insert a coin, or you can turn the knob to dispense candy. It can be in one of two states: locked or unlocked. It also tracks how many candies are left and how many coins it contains.

[外链图片转存失败(img-Amx9zUUg-1564306192450)(http://oxaz2p2ac.bkt.clouddn.com/Screen Shot 2017-10-04 at 10.21.02 PM.png)]

  • 标准答案:
    https://github.com/fpinscala/fpinscala/blob/master/answerkey/state/11.answer.scala
  • 我的解法: https://github.com/haiboself/Scala-learning/tree/master/src/PureFunctionalState
  • 参考资料:
    • https://stackoverflow.com/questions/41012375/functional-programing-in-scala-simulatemachine/41328969#41328969
    • https://groups.google.com/forum/#!topic/scala-functional/8iHaSFPPjgw
package PureFunctionalState

import datastruct._

sealed trait Input
case object Coin extends Input
case object Turn extends Input

case class Machine(locked: Boolean, candies: Int, coins: Int) {

  
  /** 有副作用的实现
    * 实际上Machine的状态就是自己的属性,所以返回值类型Machine实际上既是值又是状态
    * 所以这个实现应该可以看作纯函数式的实现
    */
  def simulateMachine(inputs: List[Input]): Machine = {
    List.foldLeft(inputs,Machine(locked,candies,coins))((x, y) => (x,y) match {
      case (Coin,Machine(true,cands,x)) if cands>0 => Machine(false,cands,x)
      case (Turn,Machine(false,cands,x)) if cands>0 => Machine(true,cands-1,x+1)
      case _ => y
    }
    )
  }
  
  /** 纯函数式实现。
    * modify返回State[Unit,Machine]。modify从旧状态生成新状态,也就是据inputs的每个输入得到一个新的Machine
    * map返回List[State[Unit,Machine]]
    * sequence返回State[List[Unit],Machine]
    */
  def simulateMachine3(inputs: List[Input]): State[(Int, Int),Machine] = for {
    _ <- State.sequence(List.map(inputs)(i => State.modify((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)
    })))
    s <- State.get
  } yield (s.candies, s.coins)

}

object TestMachine{
  def main(args: Array[String]): Unit = {
    val machine = Machine(true,10,0)
    val ls = List(Coin,Turn,Turn,Coin,Turn,Coin,Coin,Turn)

    println(machine.simulateMachine(ls))
    println(machine.simulateMachine3(ls).run(machine))
  }
}

你可能感兴趣的:(scala函数式编程笔记: 纯函数式状态)