纯函数式状态(1)

想让状态更新恢复引用透明的关键是让状态更新是显示的。不要以副作用方式更新状态,而是连同生成值一起返回一个新的状态。
纯函数式随机数生成器:

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
写一个函数使用RNG.nextInt生成0和Int.MaxValue之间(含)的随机数。

  def nonNegativeInt(rng: RNG): (Int, RNG) = {
    val (v, rng1) = rng.nextInt
    if (v == Int.MaxValue) (0, rng1)
    else (Math.abs(v), rng1)
  }

练习 6.2
写一个函数生成0到1之间的Double数。

  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
写一个函数生成一个(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
写一个函数生成一组随机数。

  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))
  }

状态行为更好的API
回顾我们上述练习的实现,会注意到一个通用的模式:我们每个函数都有一个形式为RNG => (A, RNG)的类型。这种类型的函数被称为状态行为(state action)或状态转移。状态行为可以通过组合子来组合,组合子是一个高阶函数。既然需要一只乏味地重复传递状态参数,何不用组合子帮我们在行为之间自动传递。为了便于学习讨论,我们对RNG状态行为数据类型定义一个类型别名:

  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以更为优雅的方式重新实现double函数,参见练习6.2。

  def double1(rng: RNG): (Double, RNG) =
    map(nonNegativeInt){i =>
      if (i == Int.MaxValue) 0.0 else i.toDouble / Int.MaxValue
    }(rng)

组合状态行为
练习 6.6
按照下面的函数签名写一个map2函数。这个函数接受两个行为,ra和rb,以及一个函数f用于组合它们的结果,并返回一个组合了两者的新行为。

  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组合子重新实现intDouble和doubleInt函数

  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转换行为,那么同样也可以组合一个行为列表。实现sequence函数将一个转换行为列表组合成一个转换行为。使用它实现之前写过的ints函数。

  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)

嵌套状态行为
map和map2组合子允许我们以特别简洁优雅的方式实现,其它实现方式则显得乏味且容易产生错误,但是所有的函数都可以使用map和map2组合子实现,有时候我们需要连续传递RNG,这时最好的方式使用一个新的组合子帮我们传递,所以我们需要一个更强大的组合子flatMap。
练习 6.8
实现flatMap然后用它实现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
根据flatMap重新实现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)
      }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,233评论 6 495
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,357评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,831评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,313评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,417评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,470评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,482评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,265评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,708评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,997评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,176评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,503评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,150评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,391评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,034评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,063评论 2 352