docs(course): fix review findings in modules 00–05
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1d0b8c3a5c
commit
ab8abaaa77
+3
-3
@@ -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.
|
||||
|
||||
@@ -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`).
|
||||
|
||||
@@ -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?
|
||||
|
||||
Reference in New Issue
Block a user