読者です 読者をやめる 読者になる 読者になる

新人SEの学習記録

14年度入社SEの学習記録用に始めたブログです。気づけば社会人3年目に突入。

学習記録:Scala関数型デザイン 第6章

学習記録 関数型 プログラミング Scala

第6章:純粋関数型の状態(続き)

状態の処理に適したAPI

ここまでの実装を振り返ると,どの関数でもRNG => (A, RNG)形式の型が使用されている。
この種の関数は,RNGの状態を遷移させることから状態アクションまたは状態遷移と呼ばれる。
これらの状態アクションは,コンビネータと呼ばれる高階関数を使って組み合わせることができる。
状態を明示的に渡すのは手間のかかる上に繰り返しが多いので,状態の受け渡しをコンビネータに任せて自動化する。

アクションの型について説明するときにややこしくならないよう,
ここでは状態アクションデータ型であるRNGの型エイリアスを作成する。

type Rand[+A] = RNG => (A, RNG)

Rand[A]型の値については,「ランダムに生成されたA」として考えることもできるが,正確ではない。
正確には,状態アクション=RNGを使ってAを生成し,後から別のアクションに利用できる状態にRNGを生成させる,
RNGに依存するプログラムといえる。

これで,RNGのnextIntなどのメソッドを新しい型の値に変換できる。

val int: Rand[Int] = _.nextInt

これにより,RNG.intは「RNG型を引数に整数乱数Aと次の状態のRNGを返す」関数を返す。

scala> RNG.int(rng)
res5: (Int, RNG) = (85414255,SimpleRNG(5597708669585))

次に,RNGの状態を明示的にやりとりするのを回避した上で,Randのアクションを結合するためのコンビネータを記述する。
これは,受け渡しのすべてを自動的に行うドメイン固有の言語のようなものになる。
例えば,unitアクションはRNGの単純な状態遷移であり,RNGの状態を未使用のまま渡し,常に乱数値ではなく定数値を返す。

  def unit[A](a: A): Rand[A] =
    rng => (a, rng)

unitもintと同様「RNG型を引数に整数乱数Aと次の状態のRNGを返す」関数を返すが,
返すのはunitの引数aと,引数にしたRNG型をそのまま返す。

scala> RNG.unit(1)(rng)
res9: (Int, RNG) = (1,SimpleRNG(222))

scala> RNG.unit(2)(rng)
res10: (Int, RNG) = (2,SimpleRNG(222))

状態そのものを変化させずに状態アクションの出力を変換するmapもある。
Rand[A]がRNG => (A, RNG)の型エイリアスであることを考えると,これは関数合成のようなものになる。

  def map[A,B](s: Rand[A])(f: A => B): Rand[B] =
    rng => {
      val (a, rng2) = s(rng)
      (f(a), rng2)
    }

mapの使用例は以下のようになる。
0以上Int.MaxValu以下の自然数を返すnonNegativeIntを再利用し,2で割り切れる0以上のIntを生成する。

  def nonNegativeEven: Rand[Int] =
    map(nonNegativeInt)(i => i - i % 2)

使うと以下のようになる。

scala> val rng = RNG.SimpleRNG(222)
rng: RNG.SimpleRNG = SimpleRNG(222)

scala> val (n1, rng2) = RNG.nonNegativeEven(rng)
n1: Int = 85414254
rng2: RNG = SimpleRNG(5597708669585)

scala> val (n2, rng3) = RNG.nonNegativeEven(rng2)
n2: Int = 1184694134
rng3: RNG = SimpleRNG(77640114826696)

scala> val (n3, rng4) = RNG.nonNegativeEven(rng3)
n3: Int = 1729905572
rng4: RNG = SimpleRNG(113371091627571)

scala> val (n4, rng5) = RNG.nonNegativeEven(rng4)
n4: Int = 1432996926
rng5: RNG = SimpleRNG(93912886623682)
Exercise 6.5
  • mapを使ってdoubleを要領よく実装しなおせ。

元のdoubleは以下。

  def double(rng: RNG): (Double, RNG) = {
    val (n, rng2) = nonNegativeInt(rng)
    (n / (Int.MaxValue.toDouble + 1), rng2)
  }

mapを使うと,nonNegativeIntで生成された乱数iを,Int.MaxValue+1で割ればよいので,
以下のように書き換えられる。

  def doubleViaMap: Rand[Double] =
    map(nonNegativeInt)(i => i / (Int.MaxValue.toDouble	+ 1))

使うと以下のようになる。

scala> val rng = RNG.SimpleRNG(168)
rng: RNG.SimpleRNG = SimpleRNG(168)

scala> val (d1, rng2) = RNG.doubleViaMap(rng)
d1: Double = 0.030099328141659498
rng2: RNG = SimpleRNG(4236103858067)

scala> val (d2, rng3) = RNG.doubleViaMap(rng2)
d2: Double = 0.39285430824384093
rng3: RNG = SimpleRNG(226185648061346)

scala> val (d3, rng4) = RNG.doubleViaMap(rng3)
d3: Double = 0.8836551457643509
rng4: RNG = SimpleRNG(157111570886661)