header source
my icon
esplo.net
ぷるぷるした直方体
Cover Image for Scala Functional Design & Programming - Chapter 6 (6)

Scala Functional Design & Programming - Chapter 6 (6)

about13mins to read

I bought "Scala Functional Design & Programming - A Comprehensive Guide to Functional Programming by Scalaz Contributors".
I'll read it bit by bit and summarize each chapter's content.

amazon img

This time, it's Chapter 6. This chapter discusses how to handle state in functional programming.
The question that comes to mind is, "How can we handle state in a purely functional way?"
The answer is to pass the state as an argument and return it, just like we did with lists.

6 Chapter

This chapter explains how to handle state.
The first exercise is to create a function that generates a non-negative integer using nextInt.

Create a function that returns a non-negative integer using nextInt. Note that if nextInt returns Int.MinValue, we need to handle it as a special case.

We can handle negative numbers by adding 1 to them, making all numbers in the range [0, Int.MaxValue] appear twice.

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

Create a function that returns a Double value in the range [0, 1).

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

Create a function that returns a tuple with various types.

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)
}

When I thought it was getting tedious to write so much code, the next exercise appeared.

Create a function that generates a list of random integers.

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)
}

Here, the Rand[+A] type is introduced, which takes an RNG and returns a value and the next RNG.
This type is convenient for handling random number generation.

Implement map using Rand.

// 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))

It's much simpler now.

Next, we're told to implement map2, which combines two Rand actions.

Implement 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)
  }

The implementation is simple.

[Difficult] Implement sequence to combine a list of state transitions into one.

I was stuck on this problem, but the answer was simple.
We can use Rand to implement sequence.

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))

The RNG is being hidden more and more.
Next, we're told to implement flatMap and use it to implement nonNegativeLessThan.

Implement flatMap and use it to implement nonNegativeLessThan.

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)
  })
}

The state transitions are being automated.
In the next problem, we're told to reimplement map and map2 using flatMap.

Reimplement map and map2 using flatMap.

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)))

It's much simpler now.

Next, the State type is introduced to generalize state transitions.

Generalize unit, map, map2, flatMap, and sequence using State.

I had to peek at the answer because it was difficult.

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)(_ :: _))
}

This part was difficult to understand, so I'll come back to it later.

Finally, we're given a difficult problem.

[Difficult] Implement a vending machine.

I looked at the answer because it was difficult.
The solution uses sequence and is quite abstract.

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)
}

Thoughts

I've been writing stateless code for a while, but now I can finally handle state.
If I can master this, I can write stateful code in a stateless way, which is extremely powerful.
I feel like I'm getting closer to the essence of functional programming.

However, this book is only about 1/3 done, and I still have a lot to learn.
I'll keep going and come back to this chapter later.

Share