【scala函数式编程】纯函数式状态

1. 问题的引出:实现无副作用的随机数生成器

a. 需要在调用过程中更新状态,涉及到副作用
b. 计算下一个状态时应当不改变任何值

case class RNG(var ori_seed:Int){
  private var seed:Int = ori_seed // 副作用的来源——需要更新状态,想要消除副作用,可以在每次调用方法时返回新的RNG
  def nextInt:Int = {
    seed = seed + 1 // 副作用:原地修改变量seed
    val n = seed * 2
        n
  }
}

2. 有状态的纯函数

遵循引用透明原则:应当显式更新状态,以新的值作为状态,而不是以副作用的方式原地更新状态

a. 状态处理:避免原地修改状态,而是返回一个携带新状态的随机数生成器

trait RNG{
  def nextInt:(Int, RNG) //返回随机数的同时,返回一个新的随机数生成器,而不是在原地更新原来的随机数生成器状态
}

b. 随机数的生成:使用携带状态的随机数生成器构造伪随机数

case class SimpleRNG(seed: Int) extends RNG{
  def nextInt:(Int, RNG) = {
    val newSeed = seed + 1 // 更新状态,注意不要在修改变量(产生副作用)
    val nextRNG = SimpleRNG(newSeed) // 根据新状态新建一个随机数生成器
    val n = seed * 2 // 使用种子生成一个伪随机数
    (n, nextRNG) // 返回新的随机数和新的随机数生成器
  }
}

c. 纯函数总结:

  1. 不原地修改变量,如果需要,则返回新的变量
  2. 不原地修改状态,如果需要,则返回新的状态
  3. 携带状态的函数式对象,一定会在内部生成一个新的对象

3. 可变状态的纯函数

a. 有状态的过程式代码重构为函数式代码范例:

class Foo{ // 过程式代码
  private var state:State = ... // 状态,每次调用Foo的方法都会更新状态
  def bar: Bar = ... // 调用后更新状态s
  def baz: Int = ... // 调用后更新状态s
}

trait Foo{ // 函数式代码,不在原地修改变量和对象成员
  def bar: (Bar, Foo) = ... // 每次调用会产生一个携带新的状态的Foo
  def baz: (Int, Foo) = ... // 每次调用会产生一个携带新的状态的Foo
}

b. 改进原来的RNG类

case class RNG(var ori_seed:Int){
  private var seed:Int = ori_seed // 副作用的来源——需要更新状态,想要消除副作用,可以在每次调用方法时返回新的RNG
  def nextInt:Int = {
    seed = seed + 1 // 副作用:原地修改变量seed
    val n = seed * 2
    n
  }
}

trait RNG{
  def nextInt:(Int, RNG) // 不再原地更新状态,而是选择返回一个新的RNG
}

case class SimpleRNG(seed: Int) extends RNG{
  def nextInt:(Int, RNG) = {
    val newSeed = seed + 1
    val newRNG = SimpleRNG(newSeed)
    val n = seed * 2
    (n, newRNG)
  }
}

4. 状态行为的组合

scala命令行查看s1.nextInt:(Int, RNG)的方法类型(以冒号开头以下划线结尾):type test _

组合子是柯里化的函数组合,将函数作为参数传入函数返回一个新的函数

a. 组合模式

  1. 构造状态转移函数:RNG => (A, RNG),状态转移,即接受一个RNG对象,返回一个值和一个新的RNG对象
type Rand[+A] = RNG => (A, RNG) // 使用type定义抽象类型,相当于类型的缩写
  1. 状态转移函数的使用:
def unit[A](a: A):Rand[A] = rng => (a, rng) // 可以使用柯里化理解,当传入a后,会得到Rand[A]类型(即RNG =>(A, RNG))的函数
val test = unit(1) // 返回函数类型Rand[Int] = <function1>
val s1 = SimpleRNG(2) // 传入RNG类型,会继续计算RNG => (A, RNG),返回元组(a, rng)
test(s1)
  1. 状态转移函数的改进:对值进行类型转换,兼容不同类型返回值的Rand[A](即RNG => (A, RNG))
def map[A, B](s: Rand[A])(f: A => B):Rand[B] = 
rng => {
 val (a, rng1) = s(rng)
 (f(a), rng1)
 }
val s:RNG =>(String, RNG) = (rng: RNG) => ("1", rng)
val f:String => Int = (i: String) => i.toInt
val test = map(s)(f)
val s1 = SimpleRNG(2)
test(s1)

5. 通用状态行为数据类型

对状态转移函数进行进一步抽象

  1. 原本的状态转移函数:
type Rand[+A] = RNG => (A, RNG)
  1. 通用的状态转移函数:
type State[S, +A] = S => (A, S)
type Rand[+A] = State[RNG, A] // 等号右方的泛型不能带+

6. 纯函数式命令编程

  1. 函数式代码: // 代码简洁
val loop3 =(int:List[Int]) =>  int.map(x => int.map(y => int.map(z => (x,y,z))).flatten).flatten
  1. 命令式代码: //代码易读
val loop3 = for{
x <- int
y <- int
z <- int
}yield (x, y, z)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鱼摆摆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值