想讓狀態(tài)更新恢復引用透明的關鍵是讓狀態(tài)更新是顯示的。不要以副作用方式更新狀態(tài)币狠,而是連同生成值一起返回一個新的狀態(tài)游两。
純函數(shù)式隨機數(shù)生成器:
trait RNG {
def nextInt: (Int, RNG)
}
case class SimpleRNG(seed: Long) extends RNG {
override def nextInt: (Int, RNG) = {
val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL
val newRNG = SimpleRNG(newSeed)
val n = (newSeed >>> 16).toInt
(n, newRNG)
}
}
練習 6.1
寫一個函數(shù)使用RNG.nextInt生成0和Int.MaxValue之間(含)的隨機數(shù)。
def nonNegativeInt(rng: RNG): (Int, RNG) = {
val (v, rng1) = rng.nextInt
if (v == Int.MaxValue) (0, rng1)
else (Math.abs(v), rng1)
}
練習 6.2
寫一個函數(shù)生成0到1之間的Double數(shù)漩绵。
def double(rng: RNG): (Double, RNG) = {
val (i, rng1) = nonNegativeInt(rng)
if (i == Int.MaxValue) (0.0, rng1)
else (i.toDouble / Int.MaxValue, rng1)
}
練習 6.3
寫一個函數(shù)生成一個(Int, Double)對贱案,一個(Double, Int)對和一個(Double, Double, Double)三元組。
def intDouble(rng: RNG): ((Int, Double), RNG) = {
val (i1, rng1) = rng.nextInt
val (i2, rng2) = rng1.nextInt
((i1, i2.toDouble), rng2)
}
def doubleInt(rng: RNG): ((Double, Int), RNG) = {
val ((i, d), rng1) = intDouble(rng)
((d, i), rng1)
}
def double3(rng: RNG): ((Double, Double, Double), RNG) = {
val ((_, d1), rng1) = intDouble(rng)
val ((_, d2), rng2) = intDouble(rng1)
val ((_, d3), rng3) = intDouble(rng2)
((d1, d2, d3), rng3)
}
練習 6.4
寫一個函數(shù)生成一組隨機數(shù)止吐。
def ints(count: Int)(rng: RNG): (List[Int], RNG) = {
def loop(count: Int, res: (List[Int], RNG)): (List[Int], RNG) = count match {
case 0 => res
case _ =>
val (li, rng) = res
val (i, rng1) = rng.nextInt
loop(count - 1, (i :: li, rng1))
}
loop(count, (Nil, rng))
}
狀態(tài)行為更好的API
回顧我們上述練習的實現(xiàn)宝踪,會注意到一個通用的模式:我們每個函數(shù)都有一個形式為RNG => (A, RNG)的類型侨糟。這種類型的函數(shù)被稱為狀態(tài)行為(state action)或狀態(tài)轉(zhuǎn)移。狀態(tài)行為可以通過組合子來組合肴沫,組合子是一個高階函數(shù)粟害。既然需要一只乏味地重復傳遞狀態(tài)參數(shù),何不用組合子幫我們在行為之間自動傳遞颤芬。為了便于學習討論,我們對RNG狀態(tài)行為數(shù)據(jù)類型定義一個類型別名:
type Rand[+A] = RNG => (A, RNG)
val int: Rand[Int] = _.nextInt
def unit[A](a: A): 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)
}
def nonNegativeEven: Rand[Int] =
map(nonNegativeInt)(i => i - i % 2)
練習 6.5
使用map以更為優(yōu)雅的方式重新實現(xiàn)double函數(shù)套鹅,參見練習6.2站蝠。
def double1(rng: RNG): (Double, RNG) =
map(nonNegativeInt){i =>
if (i == Int.MaxValue) 0.0 else i.toDouble / Int.MaxValue
}(rng)
組合狀態(tài)行為
練習 6.6
按照下面的函數(shù)簽名寫一個map2函數(shù)。這個函數(shù)接受兩個行為卓鹿,ra和rb菱魔,以及一個函數(shù)f用于組合它們的結(jié)果,并返回一個組合了兩者的新行為吟孙。
def map2[A, B, C](ra: Rand[A], rb: Rand[B])(f: (A, B) => C): Rand[C] =
rng => {
val (a, rng1) = ra(rng)
val (b, rng2) = rb(rng1)
(f(a, b), rng2)
}
練習 利用map2組合子重新實現(xiàn)intDouble和doubleInt函數(shù)
def both[A, B](ra: Rand[A], rb: Rand[B]): Rand[(A, B)] =
map2(ra, rb)((_, _))
val int: Rand[Int] = _.nextInt
val doubles: Rand[Double] =
rng => {
val (i, rng1) = rng.nextInt
(i.toDouble, rng1)
}
def intDouble1(rng: RNG): ((Int, Double), RNG) =
both(int, doubles)(rng)
def doubleInt1(rng: RNG): ((Double, Int), RNG) =
both(doubles, int)(rng)
練習 6.7
如果你能組合兩個RNG轉(zhuǎn)換行為澜倦,那么同樣也可以組合一個行為列表。實現(xiàn)sequence函數(shù)將一個轉(zhuǎn)換行為列表組合成一個轉(zhuǎn)換行為杰妓。使用它實現(xiàn)之前寫過的ints函數(shù)藻治。
def sequence[A](fs: List[Rand[A]]): Rand[List[A]] =
rng => {
def loop(n: Int, res: (List[A], RNG)): (List[A], RNG) = n match {
case m if m < 0 => res
case _ =>
val (li, rng) = res
val (a, rng2) = fs(n)(rng)
loop(n - 1, (a :: li, rng))
}
loop(fs.length - 1, (Nil, rng))
}
def ints1(count: Int)(rng: RNG): (List[Int], RNG) =
sequence(List.fill(count)(int))(rng)
嵌套狀態(tài)行為
map和map2組合子允許我們以特別簡潔優(yōu)雅的方式實現(xiàn),其它實現(xiàn)方式則顯得乏味且容易產(chǎn)生錯誤巷挥,但是所有的函數(shù)都可以使用map和map2組合子實現(xiàn)桩卵,有時候我們需要連續(xù)傳遞RNG,這時最好的方式使用一個新的組合子幫我們傳遞倍宾,所以我們需要一個更強大的組合子flatMap雏节。
練習 6.8
實現(xiàn)flatMap然后用它實現(xiàn)nonNegativeLessThan。
def flatMap[A, B](ra: Rand[A])(f: A => Rand[B]): Rand[B] =
rng => {
val (a, rng1) = ra(rng1)
f(a)(rng1)
}
練習 6.9
根據(jù)flatMap重新實現(xiàn)map, map2高职。這也是flatMap比map和map2更強大的地方钩乍。
def map1[A, B](ra: Rand[A])(f: A => B): Rand[B] =
flatMap(ra)(a => unit(f(a)))
def map2[A, B, C](ra: Rand[A], rb: Rand[B])(f: (A, B) => C): Rand[C] =
flatMap(ra){a =>
map(rb){b =>
f(a, b)
}
}