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. 纯函数总结:
- 不原地修改变量,如果需要,则返回新的变量
- 不原地修改状态,如果需要,则返回新的状态
- 携带状态的函数式对象,一定会在内部生成一个新的对象
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. 组合模式
- 构造状态转移函数:RNG => (A, RNG),状态转移,即接受一个RNG对象,返回一个值和一个新的RNG对象
type Rand[+A] = RNG => (A, RNG) // 使用type定义抽象类型,相当于类型的缩写
- 状态转移函数的使用:
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)
- 状态转移函数的改进:对值进行类型转换,兼容不同类型返回值的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. 通用状态行为数据类型
对状态转移函数进行进一步抽象
- 原本的状态转移函数:
type Rand[+A] = RNG => (A, RNG)
- 通用的状态转移函数:
type State[S, +A] = S => (A, S)
type Rand[+A] = State[RNG, A] // 等号右方的泛型不能带+
6. 纯函数式命令编程
- 函数式代码: // 代码简洁
val loop3 =(int:List[Int]) => int.map(x => int.map(y => int.map(z => (x,y,z))).flatten).flatten
- 命令式代码: //代码易读
val loop3 = for{
x <- int
y <- int
z <- int
}yield (x, y, z)