SE情報技術研究会’s blog

http://se-info-tech.connpass.com

2016-01-09 第11回 関数型プログラミング勉強会(Scala)の振り返り part.2

第6章 純粋関数型の状態

乱数の例

  • 副作用に依存している場合、状態の更新が副作用として行われるため、再現性が低くなる。
    → テスト、合成、並列化が難しくなる。
  • seed値を持ったRNGが乱数とその乱数を生成するための新たなseedを持ったRNGを生成する
    → このシーケンスを保つことで何回実行しても常に同じ結果が生成される

本より純粋関数型のシンプルな乱数ジェネレータの例:

case class SimpleRNG(seed: Long) extends RNG {
    def nextInt: (Int, RNG) = {
        val newSeed = (seed * …略
        val nextRNG = SimpleRNG(newSeed)
        val n = (newSeed >>> 16).toInt
        (n, nextRNG)
    }
}

Exercise 6.1

GitHubには下記のようにあるが

def nonNegativeInt(rng: RNG): (Int, RNG) = {
  val (i, r) = rng.nextInt
  (if (i < 0) -(i + 1) else i, r)
}

これはabs関数の方が良いのでは?という意見も出た

def nonNegativeInt(rng: RNG): (Int, RNG) = {
  val (i, r) = rng.nextInt
  if (i == Int.MinValue) Int.MaxValue
    (Int.MaxValue, r)
  else
    (abs(i), rng)
}

abs関数を使ったケースだとInt.MaxValueが出るパターンが1つ増えるのでは? と思われたが、そこはいくつもあるケースの中の1ケースなので大した影響ではない。 また、ifの場合も0が出るパターンも増えabs関数を使った時と同様となるが、 ifの場合、absの場合と違い、乱数の範囲をしぼっても重複のパターンが消えることはない。

Exercise 6.2

GitHubにあがってる解答で問題なし。

Exercise 6.3

GitHubにあがってる解答で問題なし。

解答の最後にコメントで下記とある

There is something terribly repetitive about passing the RNG along every time. What could we do to eliminate some of this duplication of effort?

毎回RNGを渡すことの繰り返しだらけになる。 この繰り返しをなくすには何ができるか?

これは、次のExercise 6.4で解決策を示している

Exercise 6.4

GitHubにあがってる解答で問題なし。

// A tail-recursive solution
def ints2(count: Int)(rng: RNG): (List[Int], RNG) = {
  def go(count: Int, r: RNG, xs: List[Int]): (List[Int], RNG) =
    if (count <= 0)
      (xs, r)
    else {
      val (x, r2) = r.nextInt
      go(count - 1, r2, x :: xs)
    }
  go(count, rng, List())
}

go関数のxsは蓄積変数で毎回の処理結果が追加されたリストが渡される。