純函數(shù)式狀態(tài)(1)

想讓狀態(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)
      }
    }
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市怔锌,隨后出現(xiàn)的幾起案子寥粹,更是在濱河造成了極大的恐慌,老刑警劉巖产禾,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泣崩,死亡現(xiàn)場離奇詭異莺债,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門露泊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事∩驯瘢” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵罪针,是天一觀的道長彭羹。 經(jīng)常有香客問我,道長泪酱,這世上最難降的妖魔是什么派殷? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮墓阀,結(jié)果婚禮上毡惜,老公的妹妹穿的比我還像新娘。我一直安慰自己斯撮,他們只是感情好经伙,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著勿锅,像睡著了一般帕膜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上溢十,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天垮刹,我揣著相機與錄音,去河邊找鬼茶宵。 笑死危纫,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的乌庶。 我是一名探鬼主播种蝶,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼瞒大!你這毒婦竟也來了螃征?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤透敌,失蹤者是張志新(化名)和其女友劉穎盯滚,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體酗电,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡魄藕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了撵术。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片背率。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出寝姿,到底是詐尸還是另有隱情交排,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布饵筑,位于F島的核電站埃篓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏根资。R本人自食惡果不足惜架专,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嫂冻。 院中可真熱鬧胶征,春花似錦、人聲如沸桨仿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽服傍。三九已至,卻和暖如春骂铁,著一層夾襖步出監(jiān)牢的瞬間吹零,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工拉庵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留灿椅,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓钞支,卻偏偏與公主長得像茫蛹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子烁挟,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353