"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
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]] = ???
}
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 Rand
s 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 RNG
s.
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 |