# Module 4 — Randomness without side effects
> **Time:** ~4 h · **You'll edit:** JS `js/src/random.js`, `js/src/spawn.js` · Scala `scala/src/Random.scala`, `scala/src/Spawn.scala` · F# `fsharp/src/Random.fs`, `fsharp/src/Spawn.fs`
> **Tests:** JS `cd js && node --test "test/m04-*.test.js"` · Scala `cd scala && ./test.sh M04` · F# `cd fsharp && ./test.sh M04`
> **Prerequisites:** Modules 1–3
## Why this module
After the snake eats, a new apple must appear on a random free cell. The usual code is one line:
```js
const i = Math.floor(Math.random() * cells.length)
```
That line quietly destroys everything you have built so far:
- **It is not pure.** `Math.random()` takes no arguments and returns a different number each time. A function that calls it is not referentially transparent, so `spawnApple(state)` would no longer mean one thing.
- **It cannot be tested exactly.** What should the test for "eating spawns a new apple" expect? You can only check vague properties ("the apple is somewhere free").
- **It cannot be replayed.** A player reports a bug after 300 moves. You have their key presses, but not the random numbers, so you cannot reproduce the game.
The fix is an old idea: a pseudo-random generator is not random at all. It is a **deterministic function** from a state (the seed) to a number and a new state. If we make that function explicit and *carry the seed in the game state* (that is why `seed` is in `GameState`, Module 2), randomness becomes pure. Same seed, same game.
Carrying the seed by hand is tedious and error-prone, though. Fixing *that* leads you to discover `map` and `flatMap` for random generators, and to a pattern with a famous name: the **State monad**.
## Learning objectives
After this module you can:
- **explain** why `Math.random` breaks purity and testing, and how a seed fixes it;
- **describe** at a high level how mulberry32 turns a seed into a number and a new seed, and why 32-bit arithmetic must be handled carefully in each language;
- **implement** `Random = seed → (A, seed)` with `pure`, `map` and `flatMap`, and recognise it as the State monad;
- **use** Scala's for-comprehension and F#'s computation expressions as syntax for the same chaining;
- **implement** apple spawning, including the full-board edge case, and explain why "same seed ⇒ same game".
## Concepts
### Pseudo-random numbers are a pure function in disguise
A pseudo-random number generator (PRNG) keeps a small internal state. Each time you ask for a number, it computes the number from the state and updates the state. `Math.random()` does exactly this, but the state is hidden inside the JavaScript engine and seeded from something unpredictable when the program starts.
Take the hidden state out, and what remains is a plain function:
```
next : seed -> (value, nextSeed)
```
Call it with the same seed, you get the same value and the same next seed, every time. To get a *sequence* of numbers, you feed each returned seed into the next call:
```
seed0 --next--> (v1, seed1) --next--> (v2, seed2) --next--> (v3, seed3) ...
```
That is the whole trick. The numbers look random (they pass statistical tests), but they are completely determined by the first seed. Where does the *first* seed come from? From the outside world, once, at the very edge of the program (in Module 9, the runtime asks the browser's `crypto` for one). Everything after that is pure.
> **From Scala:** `scala.util.Random` and `java.util.Random` are objects with a mutable seed inside: `rng.nextInt()` changes `rng`. Even `new Random(42)` only makes the *start* reproducible; the object is still mutated by each call, so `rng.nextInt() + rng.nextInt()` is not referentially transparent. We want the seed in our hands, as a value.
### mulberry32, in plain words
The course uses a small, fast generator called **mulberry32**. You do not need to understand why its constants work (that is research), but you should understand its structure, because it is surprisingly simple:
1. **Advance a counter.** The new seed is the old seed plus a fixed odd constant, `0x6D2B79F5`, wrapped around modulo 2^32. Because the constant is odd, repeatedly adding it visits all 2^32 possible seeds before repeating. That is the *period* of the generator: about 4.3 billion numbers.
2. **Scramble the counter.** The output value is a "mix" of the new seed: a few rounds of XOR-with-a-shifted-copy and 32-bit multiplication. These operations spread every bit of the input over every bit of the output, so consecutive seeds (which differ by a constant) give outputs that look unrelated.
In other words: the seed is only a counter, and the "randomness" is a hash of the counter. In pseudo-code, with all arithmetic on unsigned 32-bit integers:
```
s = (seed + 0x6D2B79F5) mod 2^32 -- the next seed
t = imul(s xor (s >>> 15), s or 1)
t = t xor (t + imul(t xor (t >>> 7), t or 61))
value = t xor (t >>> 14) -- as an unsigned 32-bit number
return (value, s)
```
(`imul` is 32-bit multiplication, keeping only the low 32 bits; `>>>` is an unsigned right shift.) mulberry32 is fine for games and simulations, and **not** suitable for anything security-related: given one output, the next ones can be predicted.
For example, from seed `0` the first draw gives the value `1144304738` and the new seed `1831565813` (which is `0x6D2B79F5`: zero plus the constant). The next draw, from seed `1831565813`, gives `1416247` and seed `3663131626`. These are the first two cases of `fixtures/m04-random.json`.
### The same 32 bits in three languages
The three tracks must produce **bit-identical** numbers, otherwise the shared fixtures (and apple positions) would differ between languages. That is harder than it sounds, because the languages treat integers differently:
| | JavaScript | Scala | F# |
|---|---|---|---|
| Seed type | `number` (a 64-bit float) | `Long` (holds 0 until 2^32) | `uint32` |
| 32-bit multiply | `Math.imul(a, b)` | `Int` `*` wraps around | `uint32` `*` wraps around |
| Unsigned shift | `>>>` | `>>>` on `Int` | `>>>` on `uint32` (unsigned type, so logical) |
| XOR / OR | `^` / `\|` | `^` / `\|` | `^^^` / `\|\|\|` |
| Back to unsigned | `x >>> 0` | `x.toLong & 0xFFFFFFFFL` | already unsigned |
The usual bugs: in JS, forgetting that `+` and `*` on numbers do not wrap at 2^32 (so you get huge or fractional values), or that `^` and `|` produce *signed* 32-bit results (so you get negative numbers). In Scala, doing the arithmetic in `Long` (which does not wrap at 32 bits) or forgetting to convert the final `Int` back to unsigned. The fixture `m04-random.json` checks ten consecutive draws from seeds `0`, `1` and `4294967295` (the largest seed), which catches every one of these mistakes.
### Threading seeds by hand
Suppose you have `nextUint32 : seed → [value, seed]` and you want a die roll, then the sum of two dice. Written directly (JavaScript, as an illustration):
```js
const d6 = (seed) => {
const [v, s] = nextUint32(seed)
return [1 + (v % 6), s]
}
const twoDice = (seed) => {
const [a, s1] = d6(seed)
const [b, s2] = d6(s1)
return [a + b, s2]
}
```
It works, and it is pure. But look at how much of it is plumbing: every line unpacks a pair, passes the seed on, and repacks a pair. And the plumbing is easy to get wrong:
```js
const twoDiceBuggy = (seed) => {
const [a, s1] = d6(seed)
const [b, s2] = d6(s1)
const [c, s3] = d6(s1) // oops: s1 again. c always equals b.
return [a + b + c, s2] // and we return the wrong seed
}
```
Nothing stops you from reusing a seed or returning the wrong one, and the result is still "random-looking", so the bug is hard to spot. When code has the same plumbing everywhere, the functional move is to **name the pattern and write the plumbing once**.
### A type for "something that needs a seed"
Give the shape a name:
```
Random = seed -> [A, seed]
```
A `Random` is a *description* of how to produce an `A` from a seed: a recipe, not a number. `nextUint32` is a `Random`. `d6` is a `Random`. `twoDice` is a `Random`. Nothing happens until you **run** it with a seed: `run(r, seed)`.
**JavaScript**
```js
// A Random is a plain function: seed => [value, nextSeed]
export const run = (r, seed) => ...
```
**Scala**
```scala
// A case class wrapping the function; running it is r.run(seed)
final case class Random[A](run: Long => (A, Long))
```
**F#**
```fsharp
// A single-case union wrapping the function; running it is Random.run seed r
type Random<'a> = Random of (uint32 -> 'a * uint32)
```
Scala and F# wrap the function in a small type of its own (a case class, a single-case union) so that the compiler can tell a `Random` apart from any other function with the same shape, and so that `map` and `flatMap` can be attached to it. F#'s `run` takes the seed first and the `Random` last (data last), so you can write `r |> Random.run 42u`.
Now look at the plumbing in `d6` and `twoDice` again. There are only two patterns in it.
**Pattern 1: change the value, keep the seed.** `d6` runs `nextUint32`, transforms the value (`1 + v % 6`) and passes the seed through untouched. Call this **`map`**:
```
map(r, f) : Random where r : Random, f : A -> B
seed --r--> [a, s1] gives [f(a), s1]
```
**Pattern 2: run one, then choose the next based on its value, and run that with the leftover seed.** `twoDice` runs `d6`, and then runs "another `d6`, and add `a` to its result", which depends on `a`. Call this **`flatMap`**:
```
flatMap(r, f) : Random where r : Random, f : A -> Random
seed --r--> [a, s1]
a --f--> r2
s1 --r2--> [b, s2] gives [b, s2]
```
And one trivial pattern: **a value that needs no randomness**, which leaves the seed alone. Call it **`pure`**:
```
pure(a) : Random seed gives [a, seed]
```
With these, the dice need no plumbing at all, and the seed-reuse bug cannot even be written:
**JavaScript**
```js
const d6 = map(nextUint32, (v) => 1 + (v % 6))
const twoDice = flatMap(d6, (a) => map(d6, (b) => a + b))
run(twoDice, 42) // [sum, nextSeed], the same every time for seed 42
```
**Scala**
```scala
val d6: Random[Int] = Random.nextUint32.map(v => 1 + (v % 6).toInt)
val twoDice: Random[Int] = d6.flatMap(a => d6.map(b => a + b))
twoDice.run(42L)
```
**F#**
```fsharp
let d6 : Random = Random.nextUint32 |> Random.map (fun v -> 1 + int (v % 6u))
let twoDice : Random = d6 |> Random.bind (fun a -> d6 |> Random.map (fun b -> a + b))
Random.run 42u twoDice
```
Naming differences to note (the full table is in `course/reference.md`):
- JS functions take the data first: `map(r, f)`. Scala uses methods: `r.map(f)`, and `Random.pure(a)` lives in the companion object. F# functions take the data last so they fit pipelines: `r |> Random.map f`.
- F# calls `flatMap` **`bind`**, which is the traditional ML/Haskell name.
You are going to implement `pure`, `map`, `flatMap` and `run` yourself in Exercise 4.2. The diagrams above are the specification.
### You have met this before: the State monad
Look at the type again: `seed → [A, seed]`. Replace "seed" by any state type `S`:
```
State = S -> (A, S)
```
This is the **State monad**: a computation that reads a state, produces a value, and hands on a new state, with `pure` and `flatMap` to sequence such computations without writing the plumbing. Our `Random` is `State`. It is a pure, honest version of "a function that uses and updates a variable": the "variable" is threaded through explicitly, by `flatMap`, so nothing is hidden.
> **From Scala:** you have used `map` and `flatMap` on `List` and `Option`. Any type with a sensible `pure` and `flatMap` is a **monad**, and `Random` is one. "Sensible" means three laws hold:
> - **left identity:** `flatMap(pure(a), f)` behaves like `f(a)`;
> - **right identity:** `flatMap(r, pure)` behaves like `r`;
> - **associativity:** `flatMap(flatMap(r, f), g)` behaves like `flatMap(r, a => flatMap(f(a), g))`.
>
> "Behaves like" means "gives the same result when run with any seed". The tests check the two identity laws (`flatMap with pure on the left or right changes nothing`).
Because `Random` has `map` and `flatMap`, Scala's **for-comprehension** works on it directly. A for-comprehension is only syntax: the compiler rewrites it into `flatMap` and `map` calls.
**Scala**
```scala
val twoDice: Random[Int] =
for
a <- d6
b <- d6
yield a + b
// the compiler turns this into: d6.flatMap(a => d6.map(b => a + b))
```
F# has the same idea, called a **computation expression**. You define a small "builder" object that tells the compiler which function is `bind` and which is `return`, and then you can write:
**F#**
```fsharp
type RandomBuilder() =
member _.Bind(r, f) = Random.bind f r
member _.Return(a) = Random.pure a
let random = RandomBuilder()
let twoDice : Random =
random {
let! a = d6
let! b = d6
return a + b
}
```
`let!` means "run this `Random` and bind its value", which is `bind`. F#'s `async { }`, `task { }` and `seq { }` are built the same way. (The builder is optional; the course code does not need it.)
JavaScript has no such syntax for user-defined types, so you write the nested `flatMap`/`map` calls. Once you see them as "and then, with the result...", they read naturally.
### Drawing a number below `n`
To pick one of `n` free cells, you need a number in `[0, n)`. `intBelow(n)` is `nextUint32` with its value taken modulo `n`. That is a `map`.
(Strictly, `v % n` is slightly biased when 2^32 is not a multiple of `n`: small results are a tiny bit more likely. For `n` up to 400 the bias is below one in ten million, far below anything a player could notice. Libraries that need exact uniformity reject and redraw; we keep it simple and identical across tracks.)
### Spawning an apple
Now apply it to the game. `spawnApple(state)`:
1. If there already is an apple, or the game is not `Playing`, return `state` unchanged.
2. List the **free cells**: every cell of the board that is not part of the snake, in **row-major order** (row `y = 0` from left to right, then row `y = 1`, ...).
3. Draw an index `i` below the number of free cells, from `state.seed`.
4. Return the state with `apple` set to the `i`-th free cell and `seed` set to the seed the draw left behind.
Why does the order of free cells matter? Because the index `i` must mean the same cell in every track. "Row-major" is part of the contract; the fixture `free-cells-row-major-order` checks it.
A worked example from the tests. A 3 x 2 board, a one-cell snake at (0,0) about to eat the apple at (1,0), seed 0:
```
before tick after advance after spawnApple
x: 0 1 2 x: 0 1 2 x: 0 1 2
y=0 H A . y=0 b H . y=0 b H .
y=1 . . . y=1 . . . y=1 . A .
```
After `advance`, the snake is `(1,0) (0,0)` and there is no apple. Free cells, row-major: `(2,0), (0,1), (1,1), (2,1)`, four of them. From seed 0, `nextUint32` gives `1144304738`, and `1144304738 % 4 = 2`, so the apple goes to the cell at index 2: `(1,1)`. The new seed is `1831565813`. That is the test `tick: eating the apple spawns a new one in the same step`.
### Puzzle: the board is full
What happens when the snake fills the entire board? There are no free cells. Before reading on, work out what *your* `spawnApple` would do when the list of free cells is empty, in your language. What is `v % 0`?
Think about it first, then open the explanation
- **JavaScript:** `v % 0` is `NaN`, `cells[NaN]` is `undefined`, so `apple` becomes `undefined`. No crash, only a corrupt state that will cause confusing errors later.
- **Scala:** `v % 0` on integers throws `ArithmeticException: / by zero`. The game crashes at its moment of triumph.
- **F#:** `v % 0u` throws `DivideByZeroException`.
None of these is a rule of the game. A full board means the player has won. So `spawnApple` handles it explicitly: **if there are no free cells, the status becomes `Won`** (no apple, seed unchanged). This is the fixture `spawn-on-full-board`: a 3 x 2 board completely covered by a six-cell snake.
This is a pattern worth remembering: when an operation has no valid answer for some input (here: "pick one of zero cells"), find out what that input *means in the domain*, and make it an explicit, total case.
### Putting the pieces together: `tick` and `newGame`
Two small functions finish the module:
- **`tick(state)`** is one complete game step: `advance`, then `spawnApple`. If the snake ate, the apple is gone after `advance`, and `spawnApple` places a new one. If it did not eat, there still is an apple, so `spawnApple` leaves the state (and the seed!) unchanged. Test: `tick leaves the seed alone when no apple needs spawning`.
- **`newGame(seed)`** is `initialState(seed)` with its first apple already placed.
Both are compositions of functions you already have. You wrote `compose` in Module 1; in Scala, `andThen`; in F#, `>>`.
And now the payoff: `newGame(42)` is the same game every time you call it. Any sequence of `tick`s and `turn`s from it gives the same states, apples included. Module 5 turns that into replays.
## Exercises
### Exercise 4.1 — `nextUint32`
Implement mulberry32 as described above (the formula is also in the doc comment of your stub).
**JavaScript**
```js
export const nextUint32 = (seed) => ... // nextUint32(0) -> [1144304738, 1831565813]
```
**Scala**
```scala
lazy val nextUint32: Random[Long] // Random.nextUint32.run(0L) == (1144304738L, 1831565813L)
```
**F#**
```fsharp
let nextUint32 : Random // Random.run 0u Random.nextUint32 = (1144304738u, 1831565813u)
```
Fixture: `fixtures/m04-random.json`, cases `seed-0-step-1` ... `seed-4294967295-step-10`. Each step starts from the previous step's new seed.
Watch out: see the 32-bit table above. Both the value **and** the new seed must be unsigned and below 2^32. In Scala, do the arithmetic in `Int` (where `*` wraps) and convert at the end.
### Exercise 4.2 — `pure`, `map`, `flatMap`, `run`
**JavaScript**
```js
export const pure = (a) => ... // Random
export const map = (r, f) => ... // Random, (A -> B) -> Random
export const flatMap = (r, f) => ... // Random, (A -> Random) -> Random
export const run = (r, seed) => ... // [A, seed]
```
**Scala**
```scala
final case class Random[A](run: Long => (A, Long)):
def map[B](f: A => B): Random[B]
def flatMap[B](f: A => Random[B]): Random[B]
object Random:
def pure[A](a: A): Random[A]
```
**F#** (in `module Snake.Random`)
```fsharp
let run (seed: uint32) (r: Random<'a>) : 'a * uint32
let pure (a: 'a) : Random<'a>
let map (f: 'a -> 'b) (r: Random<'a>) : Random<'b>
let bind (f: 'a -> Random<'b>) (r: Random<'a>) : Random<'b>
```
(In Scala, `run` is the case class's field, so there is nothing to write for it. In F#, `pure` is a word the compiler reserves for future use; the project file silences that warning, so the name is fine.)
Tests (in `m04-random.test.js` / `M04Random` / `M04`): `pure returns its value and leaves the seed alone`, `map applies f to the value and keeps the seed from the underlying step`, `map with the identity function changes nothing`, `flatMap threads the seed through both steps`, `flatMap with pure on the left or right changes nothing`, `running a Random does not mutate anything: same seed, same answer`.
Watch out: each of these returns a **function** (JS) or a `Random(...)` wrapping one (Scala, F#). Nothing is run when you call `map` or `flatMap`; only `run` runs.
### Exercise 4.3 — `intBelow`
**JavaScript**
```js
export const intBelow = (n) => ... // Random in [0, n)
```
**Scala**
```scala
def intBelow(n: Int): Random[Int]
```
**F#**
```fsharp
let intBelow (n: int) : Random
```
Fixture: `fixtures/m04-int-below.json` (e.g. `below-6-from-seed-0`, `below-400-from-seed-42`). Test: `intBelow always lands in [0, n)`. Write it with `map`, not by unpacking pairs.
### Exercise 4.4 — `freeCells`
Every cell not covered by the snake, row-major (`y` outer, `x` inner). The apple does **not** count as occupied.
**JavaScript**
```js
export const freeCells = (state) => ... // Point[]
```
**Scala**
```scala
def freeCells(state: GameState): List[Point]
```
**F#**
```fsharp
let freeCells (state: GameState) : Point list
```
Fixture: `fixtures/m04-free-cells.json` (`free-cells-row-major-order`, `free-cells-skip-snake`, `free-cells-snake-in-second-row`, `free-cells-apple-cell-is-still-free`, `free-cells-full-board-is-empty`). Your `range` and `contains` from Module 1 are useful here.
### Exercise 4.5 — `spawnApple`
**JavaScript**
```js
export const spawnApple = (state) => ...
```
**Scala**
```scala
def spawnApple(state: GameState): GameState
```
**F#**
```fsharp
let spawnApple (state: GameState) : GameState
```
Unchanged if there is an apple or the status is not `Playing`; `Won` if no cell is free; otherwise the apple goes to `freeCells[i]` where `[i, s'] = run(intBelow(count), state.seed)`, and `seed` becomes `s'`.
Fixture: `fixtures/m04-spawn.json` (`spawn-picks-a-free-cell-and-advances-seed`, `spawn-other-seed-picks-other-cell`, `spawn-with-one-free-cell`, `spawn-on-full-board`, and four "unchanged" cases).
Watch out: check the full board **before** drawing (see the puzzle). In Scala/F#, the apple is `Some(cell)` / `Some cell`.
### Exercise 4.6 — `tick` and `newGame`
**JavaScript**
```js
export const tick = (state) => ... // advance, then spawnApple
export const newGame = (seed) => ... // initialState, then spawnApple
```
**Scala**
```scala
def tick(state: GameState): GameState
def newGame(seed: Long): GameState
```
**F#**
```fsharp
let tick (state: GameState) : GameState
let newGame (seed: uint32) : GameState
```
Fixtures: `fixtures/m04-tick.json` (`eat-then-respawn`, `plain-move-no-respawn`, `wall-hit-is-game-over-no-spawn`, `game-over-state-unchanged`) and `fixtures/m04-new-game.json` (`seed-0`, `seed-42`). Tests: `tick is spawnApple after advance`, `newGame is reproducible: same seed, same game`.
### Run
- JS: `cd js && node --test "test/m04-*.test.js"` (runs both `m04-random` and `m04-spawn`)
- Scala: `cd scala && ./test.sh M04`
- F#: `cd fsharp && ./test.sh M04`
## Hints
Exercise 4.1 (nextUint32) — nudge
Translate the pseudo-code line by line, then fix the integer behaviour. Start with seed 0: the new seed must be `1831565813`, which is `0 + 0x6D2B79F5`. Get that right first, then the value.
Exercise 4.1 (nextUint32) — stronger hint
- **JS:** `(seed + 0x6D2B79F5) >>> 0` wraps the sum into an unsigned 32-bit number. `Math.imul(a, b)` multiplies like a 32-bit machine. `^`, `|`, `>>>` work on 32-bit integers; `^` and `|` give *signed* results, so finish the value with `>>> 0`. Use `const` for each intermediate (you cannot reassign `t`; call them `t1`, `t2`).
- **Scala:** `val s = seed.toInt + 0x6D2B79F5` (Int addition wraps). Then the `Int` formula with `*`, `^`, `|`, `>>>`. Return `(value.toLong & 0xFFFFFFFFL, s.toLong & 0xFFFFFFFFL)`. Remember to wrap the function: `Random(seed => ...)`.
- **F#:** `let s = seed + 0x6D2B79F5u` (uint32 addition wraps). Then `^^^`, `|||`, `>>>`, `*` on `uint32`; literals need the `u` suffix (`1u`, `61u`). No conversion needed at the end. Wrap the function: `Random(fun seed -> ...)`.
If seed 0 passes but seed 4294967295 fails, your addition is not wrapping.
Exercise 4.1 (nextUint32) — near-solution (try the others first)
```js
export const nextUint32 = (seed) => {
const s = (seed + 0x6D2B79F5) >>> 0
const t1 = Math.imul(s ^ (s >>> 15), ___)
const t2 = t1 ^ (t1 + Math.imul(___, ___))
return [(t2 ^ (t2 >>> 14)) >>> 0, ___]
}
```
```scala
lazy val nextUint32: Random[Long] = Random { seed =>
val s = seed.toInt + 0x6D2B79F5
val t1 = (s ^ (s >>> 15)) * ___
val t2 = t1 ^ (t1 + ___ * ___)
val v = t2 ^ (t2 >>> 14)
(___, ___)
}
```
```fsharp
let nextUint32 : Random =
Random(fun seed ->
let s = seed + 0x6D2B79F5u
let t1 = (s ^^^ (s >>> 15)) * ___
let t2 = t1 ^^^ (t1 + ___ * ___)
(t2 ^^^ (t2 >>> 14), ___))
```
Exercise 4.2 (pure, map, flatMap, run) — nudge
Follow the types. `map(r, f)` must return a `Random`, which is a function from a seed. So it starts with `(seed) => ...` (JS), `Random(seed => ...)` (Scala) or `fun seed -> ...` (F#). Inside, you have a seed and `r`. What can you do with them?
Exercise 4.2 (pure, map, flatMap, run) — stronger hint
Draw the diagrams from the Concepts section and turn each arrow into a line of code. For `flatMap`: run `r` with the incoming seed to get `[a, s1]`; call `f(a)` to get the next `Random`; run *that* with `s1`; return its result as is. `run(r, seed)` in JS applies the function. In Scala, `r.run(seed)` is the field. In F#, write `run` first, by taking the function out of the union with a pattern (`let run seed (Random f) = ...` is allowed: a single-case union can be matched right in the parameter list); then use `run` inside `map` and `bind`, and wrap each result with `Random(fun seed -> ...)`.
Exercise 4.2 (pure, map, flatMap, run) — near-solution (try the others first)
```js
export const pure = (a) => (seed) => [___, ___]
export const map = (r, f) => (seed) => { const [a, s1] = r(seed); return [___, ___] }
export const flatMap = (r, f) => (seed) => { const [a, s1] = r(seed); return ___(___) }
export const run = (r, seed) => ___
```
```scala
def map[B](f: A => B): Random[B] = Random { seed => val (a, s1) = run(seed); (___, ___) }
def flatMap[B](f: A => Random[B]): Random[B] = Random { seed => val (a, s1) = run(seed); ___.run(___) }
def pure[A](a: A): Random[A] = Random(seed => (___, ___))
```
```fsharp
let run (seed: uint32) (Random f: Random<'a>) : 'a * uint32 = ___
let pure (a: 'a) : Random<'a> = Random(fun seed -> (___, ___))
let map (f: 'a -> 'b) (r: Random<'a>) : Random<'b> =
Random(fun seed -> let (a, s1) = run seed r in (___, ___))
let bind (f: 'a -> Random<'b>) (r: Random<'a>) : Random<'b> =
Random(fun seed -> let (a, s1) = run seed r in run ___ ___)
```
Exercise 4.3 (intBelow) — nudge
It is "the value of `nextUint32`, transformed". Which of your new functions transforms a value?
Exercise 4.3 (intBelow) — stronger hint
`map` over `nextUint32` with a function that takes the remainder modulo `n`. In Scala the value is a `Long` and the result an `Int`: `(v % n).toInt`. In F#, `int (v % uint32 n)`.
Exercise 4.3 (intBelow) — near-solution (try the others first)
```js
export const intBelow = (n) => map(___, (v) => ___)
```
```scala
def intBelow(n: Int): Random[Int] = nextUint32.map(v => ___)
```
```fsharp
let intBelow (n: int) : Random = nextUint32 |> map (fun v -> ___)
```
Exercise 4.4 (freeCells) — nudge
First build *all* cells in row-major order, then keep those not in the snake.
Exercise 4.4 (freeCells) — stronger hint
All cells: for each `y` in `range(height)`, for each `x` in `range(width)`, the point `(x, y)`. In JS that is `flatMap` over the rows and `map` over the columns. In Scala, a for-comprehension over two ranges (with `yield`). In F#, a list comprehension `[ for y in ... do for x in ... -> ... ]`. Then `filter` with "not `contains` in the snake".
Exercise 4.4 (freeCells) — near-solution (try the others first)
```js
export const freeCells = (state) =>
range(state.height)
.flatMap((y) => range(___).map((x) => point(___, ___)))
.filter((p) => !contains(___, p, pointEquals))
```
```scala
def freeCells(state: GameState): List[Point] =
(for y <- Util.range(state.height); x <- Util.range(___) yield Point(___, ___))
.filterNot(p => ___)
```
```fsharp
let freeCells (state: GameState) : Point list =
[ for y in 0 .. state.Height - 1 do
for x in 0 .. ___ do
{ X = ___; Y = ___ } ]
|> List.filter (fun p -> not (___))
```
Exercise 4.5 (spawnApple) — nudge
Three outcomes: unchanged, `Won`, or a new apple with a new seed. Write the guards first, in that order, then the draw.
Exercise 4.5 (spawnApple) — stronger hint
Compute `cells = freeCells(state)`. If it is empty, return the state with status `Won`. Otherwise run `intBelow(cells.length)` with `state.seed` and destructure the pair into the index and the new seed. Build the result with both `apple` and `seed` changed.
Exercise 4.5 (spawnApple) — near-solution (try the others first)
```js
export const spawnApple = (state) => {
if (state.apple !== null || state.status !== 'Playing') return state
const cells = freeCells(state)
if (cells.length === 0) return { ...state, status: ___ }
const [i, seed] = run(intBelow(___), ___)
return { ...state, apple: ___, seed }
}
```
The Scala and F# versions have the same shape: `if ... then state else` / `match cells with [] -> ... | _ -> ...`, then `val (i, s) = Random.intBelow(cells.length).run(state.seed)` / `let (i, s) = Random.run state.Seed (Random.intBelow (List.length cells))`, and `List.item i cells` picks the cell.
Exercise 4.6 (tick, newGame) — nudge
Each is one line: a function applied to the result of another.
Exercise 4.6 (tick, newGame) — stronger hint
`tick(state) = spawnApple(advance(state))`. You can write that call directly, or with your `compose` / `pipe` (JS), `andThen` (Scala) or `>>` (F#). Import `advance` from `rules` and `initialState` from `model`.
Exercise 4.6 (tick, newGame) — near-solution (try the others first)
```js
export const tick = (state) => spawnApple(___(state))
export const newGame = (seed) => ___(initialState(___))
```
```scala
def tick(state: GameState): GameState = spawnApple(___)
def newGame(seed: Long): GameState = ___(Model.initialState(___))
```
```fsharp
let tick (state: GameState) : GameState = state |> Rules.advance |> ___
let newGame (seed: uint32) : GameState = seed |> ___ |> ___
```
## Check your understanding
1. Why is `const roll = () => Math.floor(Math.random() * 6)` impure, while `d6 = map(nextUint32, v => 1 + v % 6)` is pure? Where did the "randomness" go?
2. Predict: `run(pure(7), 123)`. Then `run(map(pure(7), x => x * 2), 123)`. Then `run(flatMap(nextUint32, _ => pure('x')), 0)`. (For the last one, you know from the text what `nextUint32` does to seed 0.)
3. In the buggy `twoDiceBuggy`, which line would be impossible to write using only `map`/`flatMap`? Why?
4. `Random` and `State`: what is `S` for our generator? Name another piece of the game state that could be threaded with the same pattern.
5. Write (on paper) the Scala for-comprehension for "draw three dice and return them as a tuple", then desugar it by hand into `flatMap`/`map`.
6. What would go wrong, across tracks, if `freeCells` listed cells column-major in JS but row-major in Scala?
7. What does `spawnApple` do on a full board, and why is "status becomes `Won`" better than letting `intBelow(0)` decide?
8. Why does `tick` leave the seed unchanged when the snake did not eat? What would break in replays if every tick consumed a random number anyway? (Hint: would it break anything? Think about determinism versus "which apple appears".)
## In production
- **Haskell's** [`random`](https://hackage.haskell.org/package/random) package uses pure generators: `uniformR :: g -> (a, g)`, exactly our shape. The **State monad** pattern is described on the [Haskell wiki](https://wiki.haskell.org/State_Monad).
- **cats** (Scala) has a general [`State`](https://typelevel.org/cats/datatypes/state.html) data type: `State[S, A]` with `map`, `flatMap`, `get`, `set`. *Functional Programming in Scala* builds a `RNG`/`State` very much like this module in its chapter on purely functional state.
- **Property-based testing** libraries such as [ScalaCheck](https://scalacheck.org/) and [fast-check](https://fast-check.dev/) generate random test inputs from a seed, so that a failing case can be reproduced by re-running with the same seed. Their generators are monads like `Random`.
- **Java's** [`SplittableRandom`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/SplittableRandom.html) is a mutable object, but its algorithm (a counter plus a mixing function) has the same structure as mulberry32.
- **Games** that support replays or lockstep multiplayer use seeded PRNGs for exactly the reason in this module: same seed and same inputs give the same game.
## Further reading
- [mulberry32 and friends (Tommy Ettinger's gist)](https://gist.github.com/tommyettinger/46a874533244883189143505d203312c) — the generator's source, with notes on its quality.
- [bryc's collection of JavaScript PRNGs](https://github.com/bryc/code/blob/master/jshash/PRNGs.md) — a reference implementation of mulberry32 in JS, which the course's fixtures agree with.
- [Pseudorandom number generator (Wikipedia)](https://en.wikipedia.org/wiki/Pseudorandom_number_generator) — the general idea, periods, and why PRNGs are not for cryptography.
- [Xorshift (Wikipedia)](https://en.wikipedia.org/wiki/Xorshift) and [PCG](https://www.pcg-random.org/) — the family of shift-and-xor generators, and a well-documented modern alternative.
- [Monad (functional programming) (Wikipedia)](https://en.wikipedia.org/wiki/Monad_%28functional_programming%29) — the general definition and the laws, with the State monad as an example.
- [Functional Programming in Scala, 2nd edition](https://www.manning.com/books/functional-programming-in-scala-second-edition) — chapter 6, "Purely functional state", is this module in book form.
- [Scala: for-comprehensions](https://docs.scala-lang.org/tour/for-comprehensions.html) and [F# computation expressions](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions) — the official docs for the syntax you saw.
- [Mostly Adequate Guide](https://mostly-adequate.gitbook.io/mostly-adequate-guide/) — chapters 8 and 9 build `map` and `chain` (flatMap) for containers in JavaScript.