"Anyone who attempts to generate random numbers by deterministic means is, of course, living in a state of sin."
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.
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
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)
val simple = RNG(123)
val tup: (Int, RNG) = simple.nextInt
println(tup)
// (47324114,RNG(3101433181802))
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
[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))
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)
}
... 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.
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) = ???
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
RandNote 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]] = ???
}
How would you implement map for Rand[A]?
Given int, re-implement double using map. Ignore the edge case.
def map[A,B](ra: Rand[A])(f: A => B): Rand[B] =
rng => {
val (a, rng1) = ra(rng0)
(f(a), rng1)
}
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.
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
}
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.
RNG implicitlyRand 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.
How many steps are there from RNG(123) to RNG(256148600186669)?
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))
What does unit do?
def unit[A](a: A): Rand[A] =
rng => (a, rng)
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?
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.
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)
}
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)
}
scala> rejectionSampler(int)(_ % 5 ==0)(a)
res3: (Int, RNG) = (936386220,RNG(61367007330318))
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.
def sequence[A](fs: List[Rand[A]]): Rand[List[A]] =
fs.foldRight(unit(List[A]()))
((f, acc) => map2(f, acc)(_ :: _))
scala> sequence(List(int))(a)
res4: (List(47324114),RNG(3101433181802))
scala> sequence(List(int,int))(a)
res5: (List(47324114, -386449838),RNG(256148600186669))
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)
http://eed3si9n.com/herding-cats/State.html
Generalize to StateT
> [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]
| Lecture 6: Purely Functional State | 1 |
|---|---|
| - | 2 |
| - | 3 |
| - | 4 |
| - | 5 |
| - | 6 |
| Exercise | 7 |
| - | 8 |
| - | 9 |
| - | 10 |
| Exercise | 11 |
| - | 12 |
Combinators on Rand |
13 |
| Exercise | 14 |
| - | 15 |
| - | 16 |
| - | 17 |
| - | 18 |
Passing RNG implicitly |
19 |
| Exercise | 20 |
| - | 21 |
| Exercise | 22 |
| - | 23 |
| - | 24 |
| - | 25 |
| - | 26 |
| - | 27 |
| - | 28 |
| - | 29 |
| - | 30 |
| Next steps | 31 |
| - | 32 |
| - | 33 |
| Table of Contents | t |
|---|---|
| Exposé | ESC |
| Full screen slides | e |
| Presenter View | p |
| Source Files | s |
| Slide Numbers | n |
| Toggle screen blanking | b |
| Show/hide slide context | c |
| Notes | 2 |
| Help | h |