
Scala関数型デザイン&プログラミング 〜6章 (6)
"Scala関数型デザイン&プログラミング ―Scalazコントリビューターによる関数型徹底ガイド"を買いました。
ちょっとずつ読んで、各章の内容をまとめてゆきます。

今回は6章。関数型で、如何に状態を扱うか?という話です。
今回で遂にPart1がおしまいです。
前回
6章
この章では、状態を扱う方法が説明されています。
純粋関数なのにどうやって??という疑問がまず浮かびますが、やることは同じです。
リストでは結果を格納するためのリストを引数に入れていました。
状態を遷移させたければ、引数に状態を渡して、また返してあげれば大丈夫そうです。
ここでは乱数を生成する関数を例にとっています。
恒例の早速練習問題。
nextIntを使って、非負整数を返す関数を作れ。なお、nextIntがInt.MinValueを返すと対応する値がないので、特殊ケースとして対応すること。
負の場合は+1してあげることで、[0, Int.MaxValue]の全てが2回登場するようにできます。
  def nonNegativeInt(rng: RNG): (Int, RNG) = {
    val (i, next) = rng.nextInt
    if(i < 0)
      (-(i + 1), next)
    else
      (i, next)
  }
[0, 1)のDouble値を返す関数を作れ。
  def double(rng: RNG): (Double, RNG) = {
    val (i, next) = nonNegativeInt(rng)
    (i.toDouble / (Int.MaxValue.toDouble +1), next)
  }
様々な型を持ったタプルを作る関数を作れ。
  def intDouble(rng: RNG): ((Int,Double), RNG) = {
    val (i1, r1) = rng.nextInt
    val (d2, r2) = double(r1)
    ((i1, d2), r2)
  }
  def doubleInt(rng: RNG): ((Double,Int), RNG) = {
    val ((i1, d1), next) = intDouble(rng)
    ((d1, i1), next)
  }
  def double3(rng: RNG): ((Double,Double,Double), RNG) = {
    val (d1, r1) = double(rng)
    val (d2, r2) = double(r1)
    val (d3, r3) = double(r2)
    ((d1, d2, d3), r3)
  }
沢山書くのがめんどくさいな〜と思った時に以下の問題が出てきます。うまい。
ランダムな整数のリストを作成する関数を作れ。
  def ints(count: Int)(rng: RNG): (List[Int], RNG) = {
    @tailrec
    def loop(n: Int, acc: List[Int], r: RNG): (List[Int], RNG)  = {
      n match {
        case 0 => (acc, r)
        case _ =>
          val (i, nr) = r.nextInt
          loop(n-1, i :: acc, nr)
      }
    }
    loop(count, List.empty[Int], rng)
  }
ここで、RNGを受け取って値とnextRNGを返す型、Rand[+A]が導入されます。
よく出てくるので、まとめて扱うと便利そうですね。
このRandに対してもmapが定義されますので、これを使って書き換えろという練習問題が出ます。
mapを使ってdoubleをもう少し要領よく実装しなおせ。
なんだか随分曖昧な指示です。
// before
  def double(rng: RNG): (Double, RNG) = {
    val (i, next) = nonNegativeInt(rng)
    (i.toDouble / (Int.MaxValue.toDouble +1), next)
  }
// after
  def mapDouble: Rand[Double] =
    map(nonNegativeInt)(_.toDouble / (Int.MaxValue.toDouble +1))
随分すっきりしました。
共通パターンになりそうですね。
と思ったら、すぐさま「mapは高性能ではありません。map2を作りましょう」などと言われて心が折れます。
map2があれば、任意のRNG型アクションの結合ができるようになります。
map2を実装せよ。
  def map2[A,B,C](ra: Rand[A], rb: Rand[B])(f: (A, B) => C): Rand[C] =
    rng => {
      val (a, rng2) = ra(rng)
      val (b, rng3) = rb(rng2)
      (f(a, b), rng3)
    }
実装自体は簡単です。
[難問] 遷移のlistを1つの遷移にまとめるためのsequenceを実装せよ
_sequenceのように書いていましたが、Randを使ってない……。
解答を見ると、1行で書かれていました。Randがちゃんと使われています。なるほど。
unitも存在を忘れていました。Randの単位元なので確かに使いドコロはここだ……。
  def _sequence[A](fs: List[Rand[A]]): Rand[List[A]] = {
    rng => {
      fs.foldRight((List.empty[A], rng))((x, acc) => {
        val (a, r2) = x(acc._2)
        (a :: acc._1, r2)
      })
    }
  }
  def sequence[A](fs: List[Rand[A]]): Rand[List[A]] =
    fs.foldRight(unit(List[A]()))((x, acc) => map2(x, acc)(_ :: _))
  def intsSeq(count: Int): Rand[List[Int]] =
    sequence(List.fill(count)(int))
どんどんとrngが隠匿されていますね。
map / map2を使ってもうまく隠匿できないケースが紹介され、さらに強力なflatMapを作れという問題が出ます。
flatMapを実装し、nonNeegativeLessThanを実装せよ。
  def flatMap[A,B](f: Rand[A])(g: A => Rand[B]): Rand[B] = {
    rng => {
      val (a, r) = f(rng)
      g(a)(r)
    }
  }
  def nonNegativeLessThan(n: Int): Rand[Int] = {
    flatMap(nonNegativeInt)(i => {
      val mod = i % n
      if(i + (n-1) - mod >= 0)
        unit(mod)
      else
        nonNegativeLessThan(n)
    })
  }
受け渡しが自動化されました。
次の問題で、より関係性がわかりやすくなります。
flatMapを使ってmap, map2を再実装せよ。
  def map[A,B](s: Rand[A])(f: A => B): Rand[B] =
    flatMap(s)(i => unit(f(i)))
  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)))
すっきりしました。
さて、このような状態の受け渡しを一般化すべく、State型が導入されます。
これを使って以下の問題が出されます。
unit, map, map2, flatMap, sequenceを一般化せよ。
一般化するだけ……とはいかず、状態遷移のイメージが難しいため答えをチラ見しながら書きました。
case class State[S,+A](run: S => (A, S)) {
  def map[B](f: A => B): State[S, B] =
    flatMap(a => State.unit(f(a)))
  def map2[B,C](sb: State[S, B])(f: (A, B) => C): State[S, C] =
    flatMap(a => sb.map(b => f(a,b)))
  def flatMap[B](f: A => State[S, B]): State[S, B] =
    State(s => {
      val (a, s2) = run(s)
      f(a).run(s2)
    }
  )
}
object State {
  type Rand[A] = State[RNG, A]
  def unit[S,A](a: A): State[S, A] = State(s => (a, s))
  def sequence[S,A](fs: List[State[S, A]]): State[S, List[A]] =
    fs.foldRight(unit[S, List[A]](List()))((x, acc) => x.map2(acc)(_ :: _))
}
この辺りはスラスラ書けるという状態には程遠いため、また振り返ることになりそうです。
今までやってきたことを鑑みると、結局は命令形プログラムと変わりありません。
各処理で状態を変更し、それを次に受け渡しているからですね。
というわけで、最後の問題が放り投げられます。
[難問] 自販機を作れ。
難しかったので答えを見ました。
抽象度が高いですが、まさしく命令形プログラミングを実現しています。
sequenceを使う辺りが解答だと少しややこしかったので、( )を使う記法にしています。
慣れてくるとスペースの方が読みやすいのかもしれません……。
object Candy {
  def update = (i: Input) => (s: Machine) =>
    (i, s) match {
      case (_, Machine(_, 0, _)) => s
      case (Coin, Machine(false, _, _)) => s
      case (Turn, Machine(true, _, _)) => s
      case (Coin, Machine(true, candy, coin)) =>
        Machine(false, candy, coin + 1)
      case (Turn, Machine(false, candy, coin)) =>
        Machine(true, candy - 1, coin)
    }
  def simulateMachine(inputs: List[Input]): State[Machine, (Int, Int)] = for {
    _ <- sequence(inputs.map((i: Input) => modify[Machine](s => update(i)(s))))
    s <- get
  } yield (s.coins, s.candies)
}
思ったこと
状態を持たない処理をずっと書いていましたが、遂に状態まで扱えるようになりました。
使いこなせれば、ステートフルな処理をステートレスに書けるという、極めて強力な記述ができそうです。
だんだんと真髄に近づいてきた感があります。
が、この本はまだ1/3程度しか終わっていません。
今回で遂にPart 1が終わり、次回からより本質的な話になっていくようです。
がんばります。
この章は消化不良なので、しばらく読んでからまた振り返りたいと思います。
