Professional Documents
Culture Documents
juanmanuel.gimeno@udl.cat
2023
Index
• Purely functional random number generation
• Working with stateful APIs
• The State data type
• Purely functional imperative programming
scala> rng.nextDouble
res1: Double = 0.9867076608154569
scala> rng.nextDouble
res2: Double = 0.8455696498024141
scala> rng.nextInt
res3: Int = -623297295
scala> rng.nextInt(10)
res4: Int = 4
scala> rng.nextInt
res5: Int = 75432356
• Clearly the code shown above is not referentially transparent
1
Purely functional random number generation
• The key idea to recover referential transparency is to make state updates
explicit
– don’t update the hidden state as a side effect
– return the new state along with the generated value
trait RNG:
def nextInt: (Int, RNG)
• Let’s define an implementation using a linear congruent generator
case class SimpleRNG(seed: Long) extends RNG:
def nextInt: (Int, RNG) =
val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL
val nextRNG = SimpleRNG(newSeed)
val n = (newSeed >>> 16).toInt
(n, nextRNG)
2
(From
FP in Scala (2nd ed), Chapter 6)
3
def randomPair(rng: RNG): (Int,Int) =
val (i1,_) = rng.nextInt
val (i2,_) = rng.nextInt
(i1,i2)
• This is ok
def randomPair(rng: RNG): ((Int,Int), RNG) =
val (i1,rng2) = rng.nextInt
val (i2,rng3) = rng2.nextInt
((i1,i2), rng3)
Exercises
• Write a function that uses RNG.nextInt to generate a random integer
between 0 and Int.MaxValue (inclusive). Make sure to handle the corner
case when nextInt returns Int.MinValue, which doesn’t have a non-
negative counterpart.
– def nonNegativeInt(rng: RNG): (Int, RNG)
• Write a function to generate a Double between 0 and 1, not including 1.
– Use Int.MaxValue and .toDouble
– def double(rng: RNG): (Double, RNG)
• Write functions to generate an (Int, Double) pair, a (Double, Int)
pair, and a (Double, Double, Double)
– def intDouble(rng: RNG): ((Int, Double), RNG)
– def doubleInt(rng: RNG): ((Double, Int), RNG)
– def double3(rng: RNG): ((Double, Double, Double), RNG)
• Write a function to generate a list of random integers.
– def ints(count: Int)(rng: RNG): (List[Int], RNG)
4
type Rand[+A] = RNG => (A, RNG)
• We can turn methods into values of this new type
val int: Rand[Int] = _.nextInt
• We want to write combinators that let us combine Rand actions while
avoiding explicitly passing along the RNG state.
– We’ll end up with a kind of domain-specific language (DSL) that
does all of the passing for us
– The unit action, which passes the RNG state through without using
it, always returning a constant value rather than a random value
def unit[A](a: A): Rand[A] =
rng => (a, rng)
– There’s also map for transforming the output of a state action without
modifying the state itself
def map[A, B](s: Rand[A])(f: A => B): Rand[B] =
rng => {
val (a, rng2) = s(rng)
(f(a), rng2)
}
– For example we can use map to generate non-negative even numbers
def nonNegativeEven: Rand[Int] =
map(nonNegativeInt)(i => i - i % 2)
5
Nesting state actions
• If we analyse the solutions to the exercises we are creating a series of
combinators (map and map2) that allows us to implement functions without
having to thread the state explicitly
• But there are functions that, with only those combinators, we cannot
implement
• One such function is nonNegativeLessThan, which generates an integer
between 0 (inclusive) and n (exclusive)
def nonNegativeLessThan(n: Int): Rand[Int]
• We can try with
def nonNegativeLessThan(n: Int): Rand[Int] =
map(nonNegativeInt)(_ % n)
but it’ll be skewed because Int.MaxValue may not be exactly divisible by
n, so numbers that are less than the remainder of that division will come
up more frequently.
• What we would like is to chain things together so that the RNG that’s
returned by nonNegativeInt is passed along to the recursive call to
nonNegativeLessThan.
6
– We could pass it along explicitly instead of using map, like this:
def nonNegativeLessThan(n: Int): Rand[Int] = { rng =>
val (i, rng2) = nonNegativeInt(rng)
val mod = i % n
if i + (n-1) - mod >= 0 then
(mod, rng2)
else nonNegativeLessThan(n)(rng2)
}
but it would be better to have a combinator that does this passing along
for us
Exercise
• Implement flatMap
– def flatMap[A, B](r: Rand[A])(f: A => Rand[B]): Rand[B]
• Use it to implement nonNegativeLessThan
– def nonNegativeLessThan(n: Int): Rand[Int]
• Reimplement map and map2 in terms of flatMap
– NOTE: The fact that this is possible is what we’re referring to when
we say that flatMap is more powerful than map and map2
7
– Outside of the defining scope anthe opaque type is unrelated to
the representation type
opaque type State[S, +A] = S => (A, S)
object State:
extension [S, A](underlying: State[S, A])
def run(s: S): (A, S) = underlying(s)
Exercises
• Generalize the functions unit, map, map2, flatMap, and sequence.
– Add them as extension methods on the State type where possible.
– Otherwise you should put them in a State companion object.
8
Purely functional imperative programming
• To facilitate this kind of imperative programming with we really only need
two primitive State combinators
– get: for reading the state and one for writing the state.
∗ def get[S]: State[S, S] = s => (s, s)
– set: for setting a new state
∗ def set[S](s: S): State[S, Unit] = _ => ((), s)
• For instance, we could use them to implement a modify combinator
def modify[S](f: S => S): State[S, Unit] =
for
s <- get
_ <- set(f(s))
yield ()
Exercise
Implement a finite state automaton that models a simple candy dispenser
• The machine has two types of input:
– insert a coin
– turn the knob to dispense candy.
• It can be in one of two states:
– locked
– unlocked.
• It also tracks how many candies are left and how many coins it contains.
enum Input:
case Coin, Turn
9
Bibliography
• Michael Pilquist, Rúnar Bjarnason and Paul Chiusano, “Functional Pro-
gramming in Scala, Second Edition”, Manning Publications (MEAP)
– Chapter 6
• Paul Chiusano, Rúnar Bjarnason, “Functional Programming in Scala”,
Manning Publications (2005)
• Exercises, hints, and answers for the book Functional Programming in
Scala
10