172 lines
9.6 KiB
Markdown
172 lines
9.6 KiB
Markdown
# 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).
|