Lecture 6: Purely Functional State

Presenter Notes



"Anyone who attempts to generate random numbers by deterministic means is, of course, living in a state of sin."

-- John von Neumann

Presenter Notes

Just as a mathematical function always calculates the same output for a given input, so does a referentially transparent function in functional programming.
In this lecture we will build an API for pseudo-random number generators that obeys this rule.
Furthermore, the combinators in our API will prevent the re-generation of the same "random" number. This particular error will not be a concern of our library's user.

Presenter Notes

We start with a linear congruential generator:

def generateSeed(seed: Long): Long =
  (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL

def generateInt(seed: Long): Int =
  (seed >>> 16).toInt







Defined in slideCode.lecture6.PseudoRandomNumberGenerator

Presenter Notes

RNG generates a "random" Integer, and another RNG, in a tuple. RNG => (Int, RNG)

case class RNG(seed0: Long) {
  def nextInt: (Int, RNG) = {
    val seed1: Long = generateSeed(seed0)
    val int1: Int = generateInt(seed1)
    val rng1: RNG = RNG(seed1)
    (int1, rng1)
  }
}





Defined in lectureCode.lecture6.PseudoRandomNumberGenerator

RNG generates a "random" Integer, and another RNG, in a tuple. RNG => (Int, RNG)

Presenter Notes

val simple = RNG(123)
val tup: (Int, RNG) = simple.nextInt
println(tup)
// (47324114,RNG(3101433181802))

Presenter Notes

Exercise

What does iterate do?

val rng0 = RNG(123)

def iterate(iterations: Int)(rng0: RNG): 
  (Int, RNG) =
  (0 to (iterations - 1)).foldLeft((0, rng0)) {
  case ((randIntI: Int, rngI: RNG), iter: Int) =>
    println(s".. $iter .. $randIntI .. $rngI")
    rngI.nextInt
  }

println(iterate(32)(rng0))




Defined in slideCode.lecture6.RNGPrimitiveExamples

Presenter Notes

[info] Running slideCode.lecture6.
         RNGPrimitiveExamples 
iter: 0 randInt: 0 rngI: RNG(123)
iter: 1 randInt: 47324114 rngI: RNG(31014..802)
iter: 2 randInt: -386449838 rngI: RNG(25614..869)
iter: 3 randInt: 806037626 rngI: RNG(52824..4818)
iter: 4 randInt: -1537559018 rngI: RNG(180..38287)
...
iter: 28 randInt: 1770503168 rngI: RNG(11316..223)
iter: 29 randInt: 265482177 rngI: RNG(1736..462)
iter: 30 randInt: -1627823703 rngI: RNG(179..50073)
iter: 31 randInt: -683498645 rngI: RNG(236..41456)
(-1680880615,RNG(171316784789787))

Presenter Notes

We can begin to manipulate RNG to generate different random types:

def nonNegativeInt(rng: RNG): (Int, RNG) = {
    val (i, r) = rng.nextInt
    (if (i < 0) -(i + 1) else i, r)
}
//Uniform on [0,1]
def double(rng: RNG): (Double, RNG) = {
    val (i, r) = nonNegativeInt(rng)
    (i / (Int.MaxValue.toDouble + 1), r)
}

Presenter Notes

... or pairs of types.

def intDouble(rng: RNG): ((Int, Double), RNG) = {
    val (i, r1) = rng.nextInt
    val (d, r2) = double(r1)
    ((i, d), r2)
}

However this is tedious and error-prone.

Presenter Notes

Exercise

What common pattern is shared between these type signatures?

def iterate(iters: Int)(rng0: RNG): (Int, RNG) = ??? 
def nonNegativeInt(rng: RNG): (Int, RNG) = ???
def double(rng: RNG): (Double, RNG) = ???
def intDouble(rng: RNG): ((Int, Double), RNG) = ???

Presenter Notes

Presenter Notes

We factor this commonality out into a type:

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


This means we can create a Rand[Int] directly from an RNG for example:

val int: Rand[Int] = _.nextInt

Presenter Notes

Combinators on Rand

Note that Rand is a type, not a class or trait, so we define our combinators in a companion object.

object Rand {
  // primitive
  def unit[A](a: A): Rand[A] = { ... }
  def flatMap[A, B](ra: Rand[A])(g: A => Rand[B]): 
    Rand[B] = { ... }

  // derived  
  def map[A,B](ra: Rand[A])(f: A => B): 
  Rand[B] = ???
  def map2[A,B,C](ra: Rand[A], rb: Rand[B])
  (f: (A, B) => C): Rand[C] = ???
  def sequence[A](fs: List[Rand[A]]): 
  Rand[List[A]] = ???
}

Presenter Notes

Exercise

How would you implement map for Rand[A]?

Given int, re-implement double using map. Ignore the edge case.

Presenter Notes

Presenter Notes

def map[A,B](ra: Rand[A])(f: A => B): Rand[B] =
    rng => {
        val (a, rng1) = ra(rng0)
        (f(a), rng1)
    }

Presenter Notes

Presenter Notes

def double: Rand[Double] = 
  map(int) {
    (i: Int) => i.toDouble / Int.MaxValue
  }

Note that no explicit RNG value is necessary anywhere in this implementation of double.

Presenter Notes

def flatMap[A, B](ra: Rand[A])(g: A => Rand[B]): 
   Rand[B] = 
   rng => {
      val (a, rng1) = ra(rng0)
      g(a)(rng1) //pass the new state along
    }

Presenter Notes

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

Note that map2 is a non-primitive combinator so we don't have to handle any RNG values explicitly.

Presenter Notes

Passing RNG implicitly

Rand passes the RNG values for us.

val simple = RNG(123)
def double: Rand[Double] = { ... }
def both[A,B](ra: Rand[A], rb: Rand[B]): Rand[(A,B)] =
    map2(ra, rb)((_, _))

println(map2(double, double)(addDoubles)(simple))
// ((0.022037,-0.179954),RNG(256148600186669))

If RNG were not passed through map2 correctly, x and y would be the same value.

Presenter Notes

Exercise

How many steps are there from RNG(123) to RNG(256148600186669)?

Presenter Notes

Presenter Notes

scala> val a = RNG(123)
a: RNG = RNG(123)
scala> a.nextInt
res0: (Int, RNG) = (47324114,RNG(3101433181802))
scala> res0._2.nextInt
res1: (Int, RNG) = (-386449838,RNG(256148600186669))

Presenter Notes

Exercise

What does unit do?

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

Presenter Notes

Presenter Notes

scala> val u = unit(1)
u: Rand[Int] = <function1>
scala> u(a)
res2: (Int, RNG) = (1,RNG(123))


What is the use of a random number generator that always returns the same number?

Presenter Notes

Presenter Notes

This implementation of map makes it a non-primitive combinator.

def map[A, B](ra: Rand[A])(f: A => B): Rand[B] =
  flatMap(ra)((a: A) => unit(f(a)))

unit is one of the primitive combinators required for the Monad typeclass.

Presenter Notes

def flatMap[A, B](ra: Rand[A])(g: A => Rand[B]): 
    Rand[B] = 
    rng => {
        val (a, rng1) = ra(rng0)
        g(a)(rng1) 
    }
def map[A,B](ra: Rand[A])(g: A => B): 
    Rand[B] =
    rng => {
        val (a, rng1) = ra(rng0)
        (g(a), rng1)
    }

Presenter Notes

Because flatMap can exit its context it can be used to implement recursive methods such as rejection sampling:

def rejectionSampler[A](ra: Rand[A])
    (p: A => Boolean): Rand[A] =
    flatMap(ra) { a =>
      if (p(a)) unit(a)
      else rejectionSampler(ra)(p)
    }

Presenter Notes

scala> rejectionSampler(int)(_ % 5 ==0)(a)
res3: (Int, RNG) = (936386220,RNG(61367007330318))

Presenter Notes

Recall that "nesting" is synonymous with flatMap, i.e. nested Rands are flatMapped together.

We will make use of rejectionSampler extensively in Friday's lab.

Presenter Notes

def sequence[A](fs: List[Rand[A]]): Rand[List[A]] =
    fs.foldRight(unit(List[A]()))
        ((f, acc) => map2(f, acc)(_ :: _))

Presenter Notes

scala> sequence(List(int))(a)
res4: (List(47324114),RNG(3101433181802))
scala> sequence(List(int,int))(a)
res5: (List(47324114, -386449838),RNG(256148600186669))

Presenter Notes

Next steps

Rand generalizes to the State monad

With Rand, we transformed the transitions between RNGs.
With State, we will transform the transitions between generic states

Today's "state" was a RNG.

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

type State[S,A] = S => (A, S)

Presenter Notes

http://eed3si9n.com/herding-cats/State.html

Generalize to StateT

Presenter Notes

State_monads

> [1..10] >>= (\x -> if odd x then x:x:[] else [x])
[1,1,2,3,3,4,5,5,6,7,7,8,9,9,10]

Presenter Notes