新人SEの学習記録

14年度入社SEの学習記録用に始めたブログです。もう新人じゃないかも…

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

第8章:プロパティベースのテスト(続き)

データ型と関数の選択

ジェネレータの意味とAPI

Gen[A]がA型の値を生成する方法を知っている何かであることは既に確定している。
それらの値を生成する方法としてはどのようなものが考えられるだろうか。

まず,それらの値をランダムに生成するという方法がある。
第6章では乱数ジェネレータRNGにインターフェイスを与えたが,
Genを乱数ジェネレータの状態遷移をラッピングする型にすることができる。

case class Gen[A](sample: State[RNG,A])
Exercise 8.4
  • Genのこの表現を使い,startからstopExecutiveの範囲内の整数を生成するGen.chooseを実装せよ。
    • def choose(start: Int, stopExecutive: Int): Gen[Int]

6章で作成したStateとRNGの関数を使用する。
min〜maxの範囲の値は,生成した乱数を(max-min)で割った余りにminを足すことで生成することができる。
乱数の生成はnonNegativeInt関数で行い,map関数で生成した乱数をmin〜maxの範囲の整数に変換する。

  // startからstopExecutiveの範囲内の整数を生成する
  def choose(start: Int, stopExecutive: Int): Gen[Int] =
    Gen(State(RNG.nonNegativeInt).map(n => start + n % (stopExecutive-start)))
Exercise 8.5
  • Genのこの表現を使い,unit, listOfNを実装せよ。

unitは常に値aを生成する。これはStateのunit関数がそのまま使用できる。

  // 常に値aを生成                                                           
  def unit[A](a: => A): Gen[A] =
    Gen(State.unit(a))

listOfNは,ジェネレータgと長さnを引数に,長さnのリストを生成する。
これは,Stateのsequence関数を使用する。
sequence関数を実装する演習問題は飛ばしてしまったが,以下のような関数になる。

  // 2つのRNG遷移のListを1つにまとめる
  def sequence[S, A](sas: List[State[S, A]]): State[S, List[A]] = {
    def go(s: S, actions: List[State[S,A]], acc: List[A]): (List[A],S) =
      actions match {
        case Nil => (acc.reverse,s)
        case h :: t => h.run(s) match { case (a,s2) => go(s2, t, a :: acc) }
      }
    State((s: S) => go(s,sas,List()))
  }

上記のsequence関数に,長さnのState[RNG, A]のリストを渡すことでlistOfNを実装できる。
ここで,標準ライブラリのList.fill(n)(x)関数を使い,xをn回繰り返すリストを生成できる。

  // ジェネレータgを使って長さnのリストを生成                                
  def listOfN[A](n: Int, g: Gen[A]): Gen[List[A]] =
    Gen(State.sequence(List.fill(n)(g.sample)))

前章で説明したように,重要となるのは小さく最も表現豊かなプリミティブを見つけ出すことである。
良い方法は,表現の対象として具体的な例を選び,必要な機能を組み立てられるか確認することである。
その際には,パターンを見つけ出してコンビネータにすることで,プリミティブを改良してみてほしい。