docs: course design spec and implementation plan
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
# Pure Snake — a functional programming course (design)
|
||||
|
||||
## Intent
|
||||
|
||||
A self-paced course that teaches functional programming by building a complete,
|
||||
playable Snake game in which **everything is pure**: the game rules, randomness,
|
||||
rendering, time, keyboard input, and the program itself. Side effects exist only
|
||||
as *values* (an `IO` type and event streams) that the student builds from scratch;
|
||||
a single `run` call at the very edge of the program is the only place anything
|
||||
actually happens.
|
||||
|
||||
**Why:** students who've had one Scala course know `map`/`filter`/`fold` and case
|
||||
classes, but rarely know how to apply them to an interactive, stateful system. The
|
||||
outcome we want is practical: by the end, a student can take a game written the
|
||||
usual way (`snake.currentPos = ...`, `apple.pos = ...`) and convert it into a purely
|
||||
functional system — and explain each step of how and why — because they have
|
||||
actually done it, not just read about it.
|
||||
|
||||
## Audience
|
||||
|
||||
- Bachelor's/master's CS students with one prior Scala course.
|
||||
- Working **alone at home**. Two supported modes:
|
||||
1. **Solo** — the text, tiered hints and tests carry them.
|
||||
2. **With an AI as a Socratic mentor** — the course ships instructions that
|
||||
make an AI assistant guide by questioning rather than handing over answers.
|
||||
- Assumed: basic JS syntax familiarity is *not* required to be strong; Module 1
|
||||
bridges from Scala.
|
||||
|
||||
## Goals
|
||||
|
||||
- A fully functioning Snake game at the end, built by the student.
|
||||
- Teaching and understanding over speed: each module explains *why*, not just *what*.
|
||||
- Test suites the student runs against their own code at every step.
|
||||
- Links to online sources for every idea used (papers, docs, blog posts, talks).
|
||||
- Three tracks: **JavaScript (primary), Scala 3, F#** — each with its own runnable
|
||||
test suites, so the course is fully doable in any of the three.
|
||||
- A transferable method: a capstone where the student converts a different game.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Not a "before" stateful version for the student to start from — the student can
|
||||
reason about stateful code themselves. (Stateful snippets may appear briefly as
|
||||
illustration in prose, but there is no stateful codebase to refactor.)
|
||||
- No FP libraries in the student's code (no Ramda, RxJS, cats-effect, fs2, FSharp
|
||||
async/observables as the implementation). Production libraries are *linked* as
|
||||
"what this looks like in the real world".
|
||||
- No TypeScript, no bundlers, no frameworks.
|
||||
- Not a course on game design or graphics.
|
||||
|
||||
## Anti-goals (failure even if it "works")
|
||||
|
||||
- Course reads as a fill-in-the-blanks exercise that can be completed by
|
||||
pattern-matching tests without understanding.
|
||||
- Impurity smuggled in "just for convenience" (a `let` that's reassigned, a
|
||||
`Math.random()` in game logic, `addEventListener` outside the edge).
|
||||
- The Scala/F# tracks feel like second-class afterthoughts with broken or
|
||||
missing tests.
|
||||
- AI mentor mode that just dumps solutions.
|
||||
|
||||
## Constraints and decided tooling
|
||||
|
||||
- **JS:** modern JavaScript, ES modules, zero dependencies. Tests with Node's
|
||||
built-in runner (`node --test`). Game renders to an HTML `<canvas>`; a thin
|
||||
`index.html` only loads the module.
|
||||
- **Scala 3:** tests with munit, run via `scala-cli` (no sbt project). Game runs
|
||||
in the browser via Scala.js (scala-cli supports it) on the same canvas page.
|
||||
- **F#:** tests with Expecto via `dotnet test` (or `dotnet run` for Expecto).
|
||||
Game runs in the browser via Fable on the same canvas page.
|
||||
- Students hand-build `IO` and streams in **all three** languages; the APIs have
|
||||
the same shape and names across tracks (adjusted for idiom, e.g. `flatMap` vs
|
||||
`bind`), documented side-by-side.
|
||||
- Where possible, pure-logic test cases live as **shared language-neutral data
|
||||
files** (JSON fixtures: given state + input → expected state) consumed by all
|
||||
three suites, so behaviour cannot drift between tracks. Tests for IO/streams
|
||||
are written natively per language but mirror each other one-to-one.
|
||||
- Reference solutions ship in a separate `solutions/` folder per track; students
|
||||
are told to try first. All test suites must pass against the solutions
|
||||
(this is how the course itself is verified).
|
||||
|
||||
## Course structure (decisions marked *[call]* were made by the designer, not the requester)
|
||||
|
||||
- **One shared text, side-by-side code.** *[call]* Each module's prose is written
|
||||
once and language-neutral where possible; every code listing, signature and
|
||||
exercise appears in all three languages (JS / Scala / F#) next to each other.
|
||||
Where a concept is language-specific (Module 1, tagged unions in Module 2), the
|
||||
module has a short per-track section. Bridges go from Scala (the shared
|
||||
background) to each track — for F#, "the Scala thing you know is spelled like
|
||||
this in F#".
|
||||
- **Canonical data model, but designed first by the student.** *[call]* Module 2
|
||||
has the student sketch their own model and answer design questions *before*
|
||||
revealing the course's canonical model, which they then adopt and compare with
|
||||
theirs. The canonical model is fixed so that the shared JSON fixtures work
|
||||
identically in every track.
|
||||
- **One growing codebase per student, per track.** *[call]* The student builds a
|
||||
single project across modules. Each module states which files it touches. If
|
||||
stuck, a student may copy the previous module's checkpoint from `solutions/` to
|
||||
continue — tests are organised per module so earlier ones keep passing.
|
||||
- **All three tracks ship together, complete.** *[call]* Following "not
|
||||
second-class": every module is finished in all three tracks, with tests passing
|
||||
against reference solutions, before the course is called done. No hour budget;
|
||||
depth is whatever the concept needs.
|
||||
- **Cross-language determinism.** The PRNG must give bit-identical sequences in JS,
|
||||
Scala and F# (32-bit integer arithmetic) so fixtures involving apples are shared.
|
||||
|
||||
## Module sequence
|
||||
|
||||
Each module contains: concept text with Scala bridges, learning objectives,
|
||||
exercises with signatures in all three languages, tiered hints (nudge → stronger
|
||||
hint → near-solution), Socratic discussion questions, test suites per track,
|
||||
"further reading" links, and a short "in production" note (cats-effect, fs2,
|
||||
Elm, Elmish, RxJS, etc.).
|
||||
|
||||
0. **Setup & how to use the course** — tracks, running tests, solo vs. mentor mode.
|
||||
1. **Purity & immutability in JS for Scala people** — referential transparency,
|
||||
`const` vs. immutable data, `Object.freeze`, spread copies, functions as values,
|
||||
currying/composition (`pipe`, `compose`).
|
||||
2. **The game as data** — `Point`, `Direction`, `Snake`, `GameState`; algebraic data
|
||||
types as tagged unions in JS (mapping from Scala `enum`/case classes and F#
|
||||
discriminated unions); smart constructors.
|
||||
3. **Pure game rules** — `step : State → State`: movement, growth, wall/self
|
||||
collision, illegal 180° turns, eating.
|
||||
4. **Randomness without side effects** — seeded PRNG as `seed → [value, seed]`;
|
||||
apple placement avoiding the snake; first encounter with the state-passing
|
||||
pattern (naming the State monad).
|
||||
5. **Folding time** — the game as `reduce` over a list of inputs; deterministic
|
||||
replays; why this makes testing trivial.
|
||||
6. **Rendering as data** — `view : State → Scene` returning a description of what
|
||||
to draw (rectangles, text), not drawing it.
|
||||
7. **The IO type** — effects as values; `of`/`map`/`flatMap`; monad laws (tested);
|
||||
interpreting a `Scene` into an `IO` of canvas operations; a test interpreter so
|
||||
IO is verifiable without a screen.
|
||||
8. **Event streams** — ticks and key presses as values; `map`/`filter`/`merge`/`scan`
|
||||
built from scratch; streams as descriptions that do nothing until run.
|
||||
9. **Wiring it together** — `main` is a single `IO` value: `scan(update)` over merged
|
||||
input + tick streams, rendered each frame; exactly one `run` at the edge. Game
|
||||
is playable.
|
||||
10. **Extensions, all pure** — score, game over, restart, pause, speed-up.
|
||||
11. **Capstone: the method** — an explicit recipe for converting a stateful game
|
||||
into a functional one, then a guided conversion of a second game (Pong or
|
||||
Tetris) with lighter scaffolding and its own tests.
|
||||
|
||||
## Socratic mentor mode
|
||||
|
||||
- A `MENTOR.md` (with thin `AGENTS.md` / `CLAUDE.md` pointers) that instructs an AI
|
||||
assistant to: ask questions before explaining, never write solution code unless
|
||||
the student explicitly gives up on an exercise, use the module's discussion
|
||||
questions and hint tiers in order, check understanding by asking the student to
|
||||
explain/predict, and consult `solutions/` only to evaluate the student's work.
|
||||
- Each module lists the discussion questions the mentor (or the solo student)
|
||||
should be able to answer before moving on.
|
||||
|
||||
## Sources
|
||||
|
||||
Each module ends with links to the ideas used. Expected anchors include: Hughes
|
||||
"Why Functional Programming Matters"; the Elm Architecture guide; Haskell/Scala
|
||||
`IO` writings (cats-effect docs, "Functional Programming in Scala"); Conal Elliott
|
||||
on FRP; "Mostly Adequate Guide to FP" (JS); Expecto/munit/Node test docs; Scala.js
|
||||
and Fable docs; PCG/xorshift/mulberry32 PRNG references; Gary Bernhardt
|
||||
"Functional Core, Imperative Shell" (as the contrast to what we're doing). Links
|
||||
must be checked as live at build time.
|
||||
|
||||
## Left to the builder
|
||||
|
||||
- Exact repository layout, file names, and module file format (Markdown assumed).
|
||||
- Exact API names for `IO`/stream operations, as long as they're consistent
|
||||
across tracks and documented side-by-side.
|
||||
- Stream implementation strategy (push vs. pull), with the choice explained to
|
||||
the student.
|
||||
- Choice of PRNG algorithm, grid size, tick rate.
|
||||
- Which capstone game (Pong or Tetris) — pick whichever teaches the method best.
|
||||
- Visual style of the game page (keep it clean and simple).
|
||||
Reference in New Issue
Block a user