docs(course): fix review findings in modules 00–05

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Lucas Winther
2026-10-29 01:04:03 +01:00
co-authored by Claude Opus 5.5
parent 1d0b8c3a5c
commit ab8abaaa77
4 changed files with 66 additions and 7 deletions
+3 -3
View File
@@ -220,7 +220,7 @@ A green test does not prove your code is right, and a red test never means you a
### The purity rules
> **The purity audit.** A test in every track (`purity.test.js`, `PurityTest`, `Purity`) scans your `src/` files (comments are ignored) and fails if it finds any of these:
> **The purity audit.** A test in every track (JS `test/purity.test.js`, Scala `PuritySuite` in `test/Purity.test.scala`, F# `tests/Purity.fs`) scans your `src/` files (comments are ignored) and fails if it finds any of these:
>
> | Track | Banned outside the runtime files |
> |---|---|
@@ -246,7 +246,7 @@ Each module builds on the previous ones. If you get stuck, work through this lis
1. Re-read the doc comment in the stub and the "Concepts" section of the module.
2. Open the failing fixture case and work it out on paper.
3. Open the module's **Hints**. Every exercise has three tiers: a *nudge*, a *stronger hint*, and a *near-solution* skeleton with holes. Open them one at a time.
3. Open the module's **Hints**. From Module 1 on, every coding exercise has three tiers: a *nudge*, a *stronger hint*, and a *near-solution* skeleton with holes. Open them one at a time. (Module 0's setup exercises have only the hints they need.)
4. Ask your mentor (human or AI), explaining what you tried.
5. Only then: **take the checkpoint.** Copy that module's files from `solutions/` into `src/` (for example `cp js/solutions/rules.js js/src/rules.js`) and continue with the next module. Because tests are organised per module, all earlier tests stay green.
@@ -269,7 +269,7 @@ Every module has the same parts, in this order:
2. **Learning objectives**: what you should be able to do afterwards.
3. **Concepts**: the ideas, with Scala bridges (blockquotes starting with **From Scala:**) and code in all three languages.
4. **Exercises**: the functions to write, with signatures in three languages, the tests and fixtures that check them, and pitfalls.
5. **Hints**: folded away; open them one tier at a time.
5. **Hints**: folded away; from Module 1 on, every coding exercise has a nudge, a stronger hint and a near-solution. Open them one tier at a time.
6. **Check your understanding**: questions you should be able to answer before moving on. Several ask you to predict an output or to say what would break if something changed.
7. **In production**: how real libraries and frameworks solve the same problem.
8. **Further reading**: links to the sources of the ideas.
+18 -4
View File
@@ -26,7 +26,7 @@ After this module you can:
### Design it yourself first
Take a sheet of paper and fifteen minutes. **Do not read past the "Stop here" line until you have written something down.** It does not matter if your answer differs from the course's; what matters is that you have thought about the questions, so that the canonical model answers questions you have actually asked.
Take a sheet of paper and fifteen minutes. **Do not read past the "Stop here" line until you have written something down, and do not open this module's stub file yet** (`js/src/model.js`, `scala/src/Model.scala`, `fsharp/src/Model.fs`): its type declarations already contain the course's answer. You will open it for the exercises, after the reveal. It does not matter if your answer differs from the course's; what matters is that you have thought about the questions, so that the canonical model answers questions you have actually asked.
Your task: write down the type of "the state of a game of Snake", in the language of your track or in pseudo-code. Use these questions to guide you:
@@ -47,9 +47,9 @@ Your task: write down the type of "the state of a game of Snake", in the languag
### The canonical model
Here is the model the course uses. All three tracks use the same fields with the same meaning, which is what lets one JSON fixture test all three.
Here is the model the course uses. (Now you may open your stub file: in Scala and F# these declarations are already written there for you, and in JS they are the JSDoc comments at the top of `model.js`.) All three tracks use the same fields with the same meaning, which is what lets one JSON fixture test all three.
**JavaScript** (JS has no types, so the shape is documented with JSDoc in `model.js`)
**JavaScript** (JS has no types, so the shape is documented with JSDoc comments)
```js
/** @typedef {{x: number, y: number}} Point */
/** @typedef {'Up'|'Down'|'Left'|'Right'} Direction */
@@ -59,7 +59,7 @@ Here is the model the course uses. All three tracks use the same fields with the
// Action: {tag:'Tick'} | {tag:'Turn', dir} | {tag:'TogglePause'} | {tag:'Restart'} | {tag:'Frame'}
```
**Scala** (already in `Model.scala`)
**Scala**
```scala
final case class Point(x: Int, y: Int)
enum Direction:
@@ -403,6 +403,20 @@ Replace `get Tick() { return todo('m02') }` by `Tick: { tag: 'Tick' }`, and so o
</details>
<details><summary>Exercise 2.4 (Action) — near-solution (try the others first)</summary>
```js
export const Action = {
Tick: { tag: ___ },
Turn: (dir) => ({ tag: ___, ___ }),
TogglePause: ___,
Restart: ___,
Frame: ___,
}
```
</details>
<details><summary>Exercise 2.5 (initialState) — nudge</summary>
This is a single record literal. Read the fixture `m02-initial-state.json`: the `expected` value is exactly the record you must build (with the seed from `args`).
+14
View File
@@ -335,6 +335,20 @@ Valid `x` values on a board of width 5 are 0, 1, 2, 3, 4. So `x >= 0` and `x < w
</details>
<details><summary>Exercise 3.2 (inBounds) — near-solution (try the others first)</summary>
```js
export const inBounds = (state, p) => p.x >= 0 && p.x < ___ && ___ && ___
```
```scala
def inBounds(state: GameState, p: Point): Boolean = p.x >= 0 && p.x < ___ && ___ && ___
```
```fsharp
let inBounds (state: GameState) (p: Point) : bool = p.X >= 0 && p.X < ___ && ___ && ___
```
</details>
<details><summary>Exercise 3.3 (turn) — nudge</summary>
Write the guard first: in which two situations does the function return `state` as it is? Then the one-field copy.
@@ -541,6 +541,20 @@ It is "the value of `nextUint32`, transformed". Which of your new functions tran
</details>
<details><summary>Exercise 4.3 (intBelow) — near-solution (try the others first)</summary>
```js
export const intBelow = (n) => map(___, (v) => ___)
```
```scala
def intBelow(n: Int): Random[Int] = nextUint32.map(v => ___)
```
```fsharp
let intBelow (n: int) : Random<int> = nextUint32 |> map (fun v -> ___)
```
</details>
<details><summary>Exercise 4.4 (freeCells) — nudge</summary>
First build *all* cells in row-major order, then keep those not in the snake.
@@ -615,6 +629,23 @@ Each is one line: a function applied to the result of another.
</details>
<details><summary>Exercise 4.6 (tick, newGame) — near-solution (try the others first)</summary>
```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 |> ___ |> ___
```
</details>
## 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?