Scala 関数型デザイン&プログラミング 6章 Exercise

結構間があいてしまった。

package fpinscala

trait RNG {
  def nextInt: (Int, RNG)
}

case class SimpleRNG(seed: Long) extends RNG {
  type Rand[+A] = RNG => (A, RNG)

  def nextInt: (Int, RNG) = {
    val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL
    val nextRNG = SimpleRNG(newSeed)
    val n = (newSeed >>> 16).toInt
    (n, nextRNG)
  }
  // 6.1
  def nonNegativeInt(rng: RNG): (Int, RNG) = {
    val (n, nextRNG) = rng.nextInt
    val next = if (n == Int.MinValue) { 0 } else { math.abs(n) }
    (next, nextRNG)
  }
  // 6.2
  def double(rng: RNG): (Double, RNG) = {
    val (n, nextRNG) = nonNegativeInt(rng)
    val next = n.toDouble / Int.MaxValue.toDouble
    (next, nextRNG)
  }
  // 6.3
  def intDouble(rng: RNG): ((Int, Double), RNG) = {
    val (n1, rng1) = rng.nextInt
    val (n2, rng2) = double(rng1)
    ((n1, n2), rng2)
  }
  // 6.3
  def doubleInt(rng: RNG): ((Double, Int), RNG) = {
    val (n1, rng1) = double(rng)
    val (n2, rng2) = rng1.nextInt
    ((n1, n2), rng2)
  }
  // 6.3
  def double3(rng: RNG): ((Double, Double, Double), RNG) = {
    val (n1, rng1) = double(rng)
    val (n2, rng2) = double(rng1)
    val (n3, rng3) = double(rng2)
    ((n1, n2, n3), rng3)
  }
  // 6.4
  def ints(count: Int)(rng: RNG): (List[Int], RNG) = {
    if (count > 0) {
      val (x, rng1) = rng.nextInt
      val (xs, rng2) = ints(count - 1)(rng1)
      (x :: xs, rng2)
    } else {
      (Nil, 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, rng2) = s(rng)
    (f(a), rng2)
  }
  // 6.5
  def double2: Rand[Double] = map(nonNegativeInt)(i => i.toDouble / Int.MaxValue.toDouble)
  // 6.6
  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)
  }
  // 6.7
  def sequence[A](fs: List[Rand[A]]): Rand[List[A]] = { rng =>
    fs.foldLeft((Nil: List[A], rng)){ (a, s) =>
      val (xs, rng1) = a
      val (x, rng2) = s(rng1)
      (x :: xs, rng2)
    }
  }
  // 6.8
  def flatMap[A, B](f: Rand[A])(g: A => Rand[B]): Rand[B] = { rng =>
    val(x, rng1) = f(rng)
    g(x)(rng1)
  }
  // 6.8
  def nonNegativeLessThan(n: Int): Rand[Int] = flatMap(nonNegativeInt){ i =>
    val mod = i % n
    if (i + (n - 1) - mod >= 0)
      rng => (mod, rng)
    else nonNegativeLessThan(n)
  }
  // 6.9
  def mapF[A, B](s: Rand[A])(f: A => B): Rand[B] = {
    flatMap(s){
      a => rng => (f(a), rng)
    }
  }
  // 6.9
  def map2F[A, B, C](ra: Rand[A], rb: Rand[B])(f: (A, B) => C) = {
    flatMap(ra) { a =>
      mapF(rb) { b => f(a, b) }
    }
  }
}

case class State[S, +A](run: S => (A, S)) {
  // 6.10
  def unit[B >: A](b: B): State[S, B] = State(st => (b, st))
  // 6.10
  def flatMap[B](g: A => State[S, B]): State[S, B] = State { st => {
      val (a, s) = this.run(st)
      g(a).run(s)
    }
  }
  // 6.10
  def map[B](f: A => B): State[S, B] = {
    this.flatMap{ a => State(st => (f(a), st)) }
  }
  def modify[S](f: S => S): State[S, Unit] = for {
    s <- get
    _ <- set(f(s))
  } yield ()
  def get[S]: State[S, S] = State(s => (s, s))
  def set[S](s: S): State[S, Unit] = State(_ => ((), s))
}
object State {
  // 6.10
  def map2[A, B, C, S](sa: State[S, A], sb: State[S, B])(f: (A, B) => C): State[S, C] = {
    sa.flatMap{ a =>
      sb.map{ b =>
        f(a, b)
      }
    }
  }
  // 6.10
  def sequence[A, S](fs: List[State[S, A]]): State[S, List[A]] = State{ st =>
    def go(s: S, xs: List[State[S, A]]): (List[A], S) = xs match {
      case Nil => (Nil, s)
      case y :: ys => {
        val (n, s1) = y.run(s)
        val (nx, s2) = go(s1, ys)
        (n :: nx, s2)
      }
    }
    go(st, fs)
  }
}

sealed trait Input
case object Coin extends Input
case object Turn extends Input

case class Machine(locked: Boolean, candies: Int, coins: Int)
object Main extends App {
  def simulateMachine(inputs: List[Input]): State[Machine, (Int, Int)] = State { machine =>
    val res = inputs.foldLeft(machine) { case (a, x) => (a, x) match {
        case (Machine(_, 0, co), _) => a
        case (Machine(true, ca, co), Coin) => Machine(false, ca, co + 1)
        case (Machine(false, ca, co), Turn) => Machine(true, ca - 1, co)
        case _ => a
      }
    }
    ((res.coins, res.candies), res)
  }
}