Files
functional-programming-snake/course/reference.md
T
Lucas WintherandClaude Fable 5.2 edd57f0148 feat(course): audit fixes and twelve extensions (Modules 7b, 12, 13)
Audit pass:
- Frame rule counts frames since the last move, so every step lasts
  exactly movesEvery(score) frames, also right after a speed-up
- clampTime + MAX_GAP_MS: a hidden tab no longer replays the whole gap
- Pong Restart keeps held keys
- Fact corrections (mulberry32 output coverage, Haskell IO, uniform)
- New fixture cases from a mutation analysis

Extensions (all three tracks, shared fixtures, course text):
- Turn queue (Exercise 10.6), persisted high score (10.7),
  replay viewer (10.8), play() end-to-end helper (9.5)
- Optional State (4.7) and optional Module 7b, stack-safe IO
- Module 12, property-based testing with generators, shrinking,
  invariants and a model-based oracle
- Module 13, Conway's Life with a Click event
- JS type checking via jsconfig, JSDoc and types.d.ts
- Maintainer tools: scripts/mutate.mjs, scripts/typecheck-js.sh,
  Playwright end-to-end suite (e2e/, scripts/e2e.sh)

Docs: docs/contract.md is the canonical contract; specs and build log
under docs/superpowers.

Verified: JS 728, Scala 689, F# 691 unit tests; stubs and progression
M01-M13 and M07b; 176 end-to-end tests on Chromium and WebKit.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 12:05:29 +01:00

32 KiB

Reference: the course's API in three languages

This page lists every type, value and function you write (or are given) in the course, side by side in JavaScript, Scala and F#. The names come straight from the stub files in js/src/, scala/src/ and fsharp/src/; when this page and a stub's doc comment disagree, the stub wins (and please report it).

Use it to look up a spelling or an argument order. It does not explain why; the modules do that.

How the three tracks differ

Topic JavaScript Scala F#
Style of calls standalone functions, data first: map(io, f) methods on the value, io.map(f), plus functions on the companion object: IO.pure(a) module functions, data last, curried: IO.map f io, so io |> IO.map f works
Where a function lives named export of a file: import { map } from './io.js' an object (Rules.turn) or a method of the type (r.flatMap(f)) a module (Rules.turn); open Snake.Model to drop the prefix
flatMap flatMap flatMap bind
filterMap on streams filterMap (function returns a value or null) filterMap (function returns an Option) choose (function returns an option, like List.choose)
Field names camelCase: nextDir camelCase: nextDir PascalCase: NextDir
Sum types strings ('Up') or tagged objects ({tag: 'Turn', dir}) enum: Direction.Up, Action.Turn(dir) discriminated unions: Up, Turn dir
"Nothing here" null None / Some(x) None / Some x
Seeds and random values number (an integer in [0, 2^32)) Long (in [0, 2^32)) uint32
Time in ms number Long int64
"No result" undefined () (Unit) () (unit)
Unimplemented stub todo('m03') ??? failwith "TODO m03"

F#'s pure triggers warning FS0046 ("reserved for future use"); the project files silence it, so the name is fine.

Module 1 — util

Concept JavaScript (js/src/util.js) Scala (scala/src/Util.scala) F# (fsharp/src/Util.fs)
numbers 0..n-1 range(n) Util.range(n: Int): List[Int] Util.range (n: int) : int list
all but the last dropLast(xs) Util.dropLast[A](xs: List[A]): List[A] Util.dropLast (xs: 'a list) : 'a list
membership with a custom equality contains(xs, p, eq), true if some eq(x, p) Util.contains[A](xs: List[A], p: A, eq: (A, A) => Boolean): Boolean Util.contains (xs: 'a list) (p: 'a) (eq: 'a -> 'a -> bool) : bool
left-to-right application pipe(x, ...fns) built in: f andThen g, or x.pipe(f) with import scala.util.chaining.* built in: x |> f, f >> g
right-to-left composition compose(...fns) built in: g compose f built in: g << f
deep freeze deepFreeze(obj) (returns the same object) not needed: data is immutable not needed: data is immutable

Module 2 — model

Concept JavaScript (js/src/model.js) Scala (scala/src/Model.scala) F# (fsharp/src/Model.fs)
a cell {x, y}, built with point(x, y) final case class Point(x: Int, y: Int) type Point = { X: int; Y: int }
point equality pointEquals(a, b) == (case classes compare structurally) = (records compare structurally)
direction 'Up', 'Down', 'Left', 'Right' enum Direction { Up, Down, Left, Right }: Direction.Up type Direction = Up | Down | Left | Right
unit step delta(dir) Model.delta(dir: Direction): Point Model.delta (dir: Direction) : Point
reverse direction opposite(dir) Model.opposite(dir: Direction): Direction Model.opposite (dir: Direction) : Direction
status 'Playing', 'Paused', 'GameOver', 'Won' enum Status: Status.Playing, ... type Status = Playing | Paused | GameOver | Won
actions Action.Tick, Action.Turn(dir), Action.TogglePause, Action.Restart, Action.Frame; values {tag: 'Tick'}, {tag: 'Turn', dir}, ... enum Action: Action.Tick, Action.Turn(dir), Action.TogglePause, Action.Restart, Action.Frame type Action = Tick | Turn of Direction | TogglePause | Restart | Frame
game state {width, height, snake, dir, nextDir, apple, seed, score, status, frame, queued} (queued: a direction or null) GameState(width, height, snake: List[Point], dir, nextDir, apple: Option[Point], seed: Long, score, status, frame, queued: Option[Direction]) { Width; Height; Snake: Point list; Dir; NextDir; Apple: Point option; Seed: uint32; Score; Status; Frame; Queued: Direction option }
fresh game initialState(seed) Model.initialState(seed: Long): GameState Model.initialState (seed: uint32) : GameState

The snake is a list of points, head first. apple is null / None when there is no apple.

Module 3 — rules

Concept JavaScript (js/src/rules.js) Scala (scala/src/Rules.scala) F# (fsharp/src/Rules.fs)
one step from a cell move(p, dir) Rules.move(p: Point, dir: Direction): Point Rules.move (p: Point) (dir: Direction) : Point
on the board? inBounds(state, p) Rules.inBounds(state: GameState, p: Point): Boolean Rules.inBounds (state: GameState) (p: Point) : bool
ask to turn turn(state, dir) Rules.turn(state: GameState, dir: Direction): GameState Rules.turn (state: GameState) (dir: Direction) : GameState
one step of the game advance(state) Rules.advance(state: GameState): GameState Rules.advance (state: GameState) : GameState

Module 4 — random and spawn

Concept JavaScript (js/src/random.js) Scala (scala/src/Random.scala) F# (fsharp/src/Random.fs)
the type a function seed => [value, nextSeed] final case class Random[A](run: Long => (A, Long)) type Random<'a> = Random of (uint32 -> 'a * uint32)
run it run(r, seed) → [a, seed2] r.run(seed) → (a, seed2) (the field) Random.run seed r → (a, seed2)
constant pure(a) Random.pure(a) Random.pure a
transform the value map(r, f) r.map(f) Random.map f r
chain flatMap(r, f) r.flatMap(f) Random.bind f r
the generator (mulberry32) nextUint32 (itself a Random: call nextUint32(seed)) Random.nextUint32: Random[Long] Random.nextUint32 : Random<uint32>
a number in [0, n) intBelow(n) Random.intBelow(n: Int): Random[Int] Random.intBelow (n: int) : Random<int>

In F#, do every * and + of the generator through uint64 helpers so it also works in the browser (Module 4, "F# in the browser").

Optional (Exercise 4.7): the same pattern for any state type S. Nothing in the game uses it.

Concept JavaScript (js/src/state.js) Scala (scala/src/State.scala) F# (fsharp/src/State.fs)
the type a function s => [value, nextS] final case class State[S, A](run: S => (A, S)) type State<'s, 'a> = State of ('s -> 'a * 's)
run it run(st, s) → [a, s2] st.run(s) → (a, s2) (the field) State.run s st → (a, s2)
constant pure(a) State.pure(a) State.pure a
transform the value map(st, f) st.map(f) State.map f st
chain flatMap(st, f) st.flatMap(f) State.bind f st
read the state get (a value) State.get[S]: State[S, S] State.get () : State<'s, 's>
replace the state put(s) (value undefined) State.put(s): State[S, Unit] State.put s : State<'s, unit>
change the state modify(f) (value undefined) State.modify(f: S => S): State[S, Unit] State.modify (f: 's -> 's) : State<'s, unit>
Concept JavaScript (js/src/spawn.js) Scala (scala/src/Spawn.scala) F# (fsharp/src/Spawn.fs)
empty cells, row-major freeCells(state) Spawn.freeCells(state): List[Point] Spawn.freeCells (state: GameState) : Point list
place an apple spawnApple(state) Spawn.spawnApple(state): GameState Spawn.spawnApple (state: GameState) : GameState
advance, then spawn tick(state) Spawn.tick(state): GameState Spawn.tick (state: GameState) : GameState
fresh game with an apple newGame(seed) Spawn.newGame(seed: Long): GameState Spawn.newGame (seed: uint32) : GameState

Module 5 — game (and its Module 10 additions)

Concept JavaScript (js/src/game.js) Scala (scala/src/Game.scala) F# (fsharp/src/Game.fs)
the update function update(state, action) Game.update(state: GameState, action: Action): GameState Game.update (state: GameState) (action: Action) : GameState
final state of a game replay(state, actions) Game.replay(state, actions: List[Action]): GameState Game.replay (state: GameState) (actions: Action list) : GameState
every state of a game history(state, actions) (length actions.length + 1) Game.history(state, actions): List[GameState] Game.history (state: GameState) (actions: Action list) : GameState list
frames per step (m10) movesEvery(score) Game.movesEvery(score: Int): Int Game.movesEvery (score: int) : int
turn with the queue (m10) steer(state, dir) Game.steer(state: GameState, dir: Direction): GameState Game.steer (state: GameState) (dir: Direction) : GameState

Module 6 — view

Concept JavaScript (js/src/view.js) Scala (scala/src/View.scala) F# (fsharp/src/View.fs)
pixels per cell CELL (20) View.CELL: Int View.CELL
a rectangle {tag: 'Rect', x, y, w, h, color} Shape.Rect(x, y, w, h, color) Rect(x, y, w, h, color) (union case of Shape)
a text {tag: 'Text', x, y, text, color, size, align} Shape.Text(x, y, text, color, size, align) Text(x, y, text, color, size, align)
a picture an array of shapes type Scene = List[Shape] type Scene = Shape list
state to picture view(state) View.view(state: GameState): Scene View.view (state: GameState) : Scene

Module 7 — IO

Concept JavaScript (js/src/io.js) Scala (scala/src/IO.scala) F# (fsharp/src/IO.fs)
operations {tag: 'Draw', shape}, {tag: 'RandomSeed'}; m10: {tag: 'LoadBest'}, {tag: 'SaveBest', score} enum Op: Op.Draw(shape), Op.RandomSeed; m10: Op.LoadBest, Op.SaveBest(score: Int) type Op = Draw of Shape | RandomSeed; m10: | LoadBest | SaveBest of int
answers to operations plain values: undefined for a draw or a save, a number for a seed or a best score enum OpResult: OpResult.Done, OpResult.Seed(value: Long); m10: OpResult.Best(value: Int) type OpResult = Done | Seed of uint32; m10: | Best of int
the IO type {tag: 'Pure', value} or {tag: 'Effect', op, k} enum IO[+A]: IO.Pure(a), IO.Effect(op, k: OpResult => IO[A]) type IO<'a> = Pure of 'a | Effect of Op * (OpResult -> IO<'a>)
finished program pure(a) IO.pure(a) IO.pure a
one operation effect(op) IO.effect(op): IO[OpResult] IO.effect op : IO<OpResult>
transform the result map(io, f) io.map(f) IO.map f io
chain flatMap(io, f) io.flatMap(f) IO.bind f io
run in order sequence(ios) IO.sequence(ios: List[IO[Unit]]): IO[Unit] IO.sequence (ios: IO<unit> list) : IO<unit>
draw one shape draw(shape) IO.draw(shape): IO[Unit] IO.draw shape : IO<unit>
ask for a seed randomSeed (a value) IO.randomSeed: IO[Long] (a value) IO.randomSeed () : IO<uint32> (a function of unit)
draw a scene drawScene(scene) IO.drawScene(scene): IO[Unit] IO.drawScene scene : IO<unit>
load the best score (m10) loadBest (a value) IO.loadBest: IO[Int] (a value) IO.loadBest () : IO<int> (a function of unit)
save the best score (m10) saveBest(score) IO.saveBest(score: Int): IO[Unit] IO.saveBest (score: int) : IO<unit>
pure test interpreter trace(io, seed) → {result, ops}; m10: trace(io, seed, best = 0) answers LoadBest with best IO.trace(io, seed): Traced[A], Traced(result, ops); m10: IO.trace(io, seed, best: Int = 0) IO.trace seed io : Traced<'a>, { Result; Ops }; m10: IO.traceWith (seed: uint32) (best: int) io, with trace seed io = traceWith seed 0 io

Module 7b — stack-safe IO (optional)

Uses the Op, OpResult and Traced of Module 7; independent of the IO type itself.

Concept JavaScript (js/src/safeio.js) Scala (scala/src/SafeIO.scala) F# (fsharp/src/SafeIO.fs, module Snake.SafeIO)
the type {tag: 'Pure', value}, {tag: 'Effect', op, k} or {tag: 'FlatMap', io, f} enum SafeIO[+A]: Pure(a: A), Effect(op: Op, k: OpResult => SafeIO[A]), FlatMap[X, B](io: SafeIO[X], f: X => SafeIO[B]) extends SafeIO[B] type SafeIO<'a> = Pure of 'a | Effect of IO.Op * (IO.OpResult -> SafeIO<'a>) | FlatMap of SafeIO<obj> * (obj -> SafeIO<'a>)
finished program pure(value) SafeIO.pure(a) SafeIO.pure a
one operation effect(op) SafeIO.effect(op): SafeIO[OpResult] SafeIO.effect op : SafeIO<IO.OpResult>
chain (only builds a node) flatMap(io, f) io.flatMap(f) SafeIO.bind f io (via the given erase, private)
transform the result map(io, f) io.map(f) SafeIO.map f io
run in order sequence(ios) SafeIO.sequence(ios: List[SafeIO[Unit]]): SafeIO[Unit] SafeIO.sequence (ios: SafeIO<unit> list) : SafeIO<unit>
one rewrite (re-association) step(io) SafeIO.step(io: SafeIO[A]): SafeIO[A] SafeIO.step (io: SafeIO<'a>) : SafeIO<'a>
loop-free driver (given) drive(start, isDone, next) → [last, outs] none (@tailrec instead) none (tail recursion instead)
one interpreter move traceStep(io, seed) → [next, ops] none none
pure test interpreter trace(io, seed) → {result, ops} SafeIO.trace(io, seed): Traced[A] SafeIO.trace seed io : IO.Traced<'a>

Module 8 — streams

Concept JavaScript (js/src/stream.js) Scala (scala/src/Stream.scala) F# (fsharp/src/Stream.fs)
inputs plain objects {tag: 'Time', t}, {tag: 'KeyDown', key}, {tag: 'KeyUp', key}; m13: {tag: 'Click', x, y} (canvas pixels) enum Event: Event.Time(t: Long), Event.KeyDown(key), Event.KeyUp(key); m13: Event.Click(x: Int, y: Int) type Event = Time of int64 | KeyDown of string | KeyUp of string; m13: | Click of int * int
the stream type { step: input => [outputs, next] } final case class Stream[A](step: Event => (List[A], Stream[A])) type Stream<'a> = Stream of (Event -> 'a list * Stream<'a>)
feed one input s.step(input) s.step(event) Stream.step e s
key presses / releases keyDowns, keyUps Stream.keyDowns, Stream.keyUps Stream.keyDowns, Stream.keyUps
mouse clicks (m13, given) clicks (emits {x, y}) Stream.clicks: Stream[(Int, Int)] Stream.clicks : Stream<int * int>
a clock interval(ms) Stream.interval(ms: Long): Stream[Long] Stream.interval (ms: int64) : Stream<int64>
transform outputs map(s, f) s.map(f) Stream.map f s
keep some outputs filter(s, p) s.filter(p) Stream.filter p s
transform and drop filterMap(s, f) (drop null) s.filterMap(f) (drop None) Stream.choose f s (drop None)
combine two streams merge(a, b) a.merge(b) Stream.merge a b
running fold scan(s, f, z) s.scan(z)(f) Stream.scan f z s
emit a first value startWith(s, v) s.startWith(v) Stream.startWith v s
run on a list of inputs run(s, inputs) s.run(events) Stream.run events s
limit clock jumps (m10) clampTime(s, maxGap) s.clampTime(maxGap: Long) Stream.clampTime (maxGap: int64) s

Module 9 — input, app, runtime (and Module 10 additions)

Concept JavaScript Scala F#
key to action keyToAction(key) → action or null (input.js) Input.keyToAction(key: String): Option[Action] Input.keyToAction (key: string) : Action option
ms per step TICK_MS = 150 (app.js) App.TICK_MS: Long App.TICK_MS (150L)
ms per frame (m10) FRAME_MS = 25 App.FRAME_MS: Long App.FRAME_MS (25L)
longest accepted time step (m10) MAX_GAP_MS = 250 App.MAX_GAP_MS: Long App.MAX_GAP_MS (250L)
actions, classic clock classicActions App.classicActions: Stream[Action] App.classicActions : Stream<Action>
actions, frame clock (m10) speedActions App.speedActions: Stream[Action] App.speedActions : Stream<Action>
states over time states(seed, actions) App.states(seed: Long, actions): Stream[GameState] App.states (seed: uint32) (actions: Stream<Action>) : Stream<GameState>
draw one state render(state) App.render(state): IO[Unit] App.render (state: GameState) : IO<unit>
a session (m10) {game, best, improved} final case class Session(game: GameState, best: Int, improved: Boolean) type Session = { Game: GameState; Best: int; Improved: bool }
one session step (m10) sessionUpdate(session, action) App.sessionUpdate(session: Session, action: Action): Session App.sessionUpdate (session: Session) (action: Action) : Session
sessions over time (m10) sessions(seed, best, actions) App.sessions(seed: Long, best: Int, actions: Stream[Action]): Stream[Session] App.sessions (seed: uint32) (best: int) (actions: Stream<Action>) : Stream<Session>
picture of a session (m10) sessionView(session) App.sessionView(session: Session): Scene App.sessionView (session: Session) : Scene
draw a session, save a new best (m10) renderSession(session) App.renderSession(session: Session): IO[Unit] App.renderSession (session: Session) : IO<unit>
the whole program main (a value) App.main: IO[Stream[IO[Unit]]] (a value) App.main () : IO<Stream<IO<unit>>> (a function of unit)
the program as data play(main, seed, events) → one list of ops per drawing; m10: play(main, seed, events, best = 0) App.play(main, seed: Long, events: List[Event]): List[List[Op]]; m10: best: Int = 0 added App.play (seed: uint32) (events: Event list) main : Op list list; m10: App.playWith (seed: uint32) (best: int) events main, with play seed events main = playWith seed 0 events main
the outside world Platform object: fillRect(x, y, w, h, color), fillText(x, y, text, color, size, align), randomSeed(), startClock(onTime), onKeyDown(cb), onKeyUp(cb); m10: loadBest() → number, saveBest(score); m13: onClick(cb), cb(x, y) in canvas pixels trait Platform with the same method names; randomSeed(): Long, startClock(onTime: Long => Unit); m10: loadBest(): Int, saveBest(score: Int): Unit; m13: onClick(cb: (Int, Int) => Unit): Unit (given) type Platform record of functions: FillRect, FillText, RandomSeed: unit -> uint32, StartClock: (int64 -> unit) -> unit, OnKeyDown, OnKeyUp; m10: LoadBest: unit -> int, SaveBest: int -> unit; m13: OnClick: (int -> int -> unit) -> unit (given)
interpret an IO runIO(io, platform) (runtime.js) Runtime.runIO(io, p) Runtime.runIO p io
run the program runApp(main, platform); m13: also registers onClick Runtime.runApp(main, p) Runtime.runApp p main
browser platform browserPlatform(canvas, win) (browser.js, you write it; m10: the best score in localStorage key pure-snake-best; m13: onClick, with the given helper canvasPoint(canvas, clientX, clientY) → {x, y}) BrowserPlatform(canvas) (scala/web/, given) BrowserPlatform.create canvas, BrowserPlatform.gameCanvas () (fsharp/web/, given)
entry point js/src/main.js (given): runApp(main, browserPlatform(...)) scala/web/Main.scala (given): SnakeMain fsharp/web/SnakeMain.fs (given)

Module 10 — replay viewer (replay)

JS js/src/replay.js, Scala scala/src/Replay.scala (types at the top level of package snake, functions in object Replay), F# fsharp/src/Replay.fs (module Snake.Replay; ViewerAction is [<RequireQualifiedAccess>]).

Concept JavaScript Scala F#
a recording {seed, actions} (e.g. fixtures/replay-sample.json) final case class Recording(seed: Long, actions: List[Action]) type Recording = { Seed: uint32; Actions: Action list }
the viewer {index, last, playing} final case class Viewer(index: Int, last: Int, playing: Boolean) type Viewer = { Index: int; Last: int; Playing: bool }
viewer actions ViewerAction.Next, .Prev, .First, .Last, .TogglePlay, .Beat; values {tag: 'Next'}, ... enum ViewerAction { Next, Prev, First, Last, TogglePlay, Beat } ViewerAction.Next, ... ViewerAction.Beat
ms per autoplay frame REPLAY_MS = 100 Replay.REPLAY_MS: Long Replay.REPLAY_MS (100L)
every state of the game frames(recording) Replay.frames(recording: Recording): List[GameState] Replay.frames (recording: Recording) : GameState list
the first viewer viewerStart(last) Replay.viewerStart(last: Int): Viewer Replay.viewerStart (last: int) : Viewer
one viewer step viewerUpdate(viewer, action) Replay.viewerUpdate(viewer: Viewer, action: ViewerAction): Viewer Replay.viewerUpdate (viewer: Viewer) (action: ViewerAction) : Viewer
key to action viewerKey(key) → action or null Replay.viewerKey(key: String): Option[ViewerAction] Replay.viewerKey (key: string) : ViewerAction option
picture viewerView(frames, viewer) Replay.viewerView(frames: List[GameState], viewer: Viewer): Scene Replay.viewerView (frames: GameState list) (viewer: Viewer) : Scene
actions over time replayActions Replay.replayActions: Stream[ViewerAction] Replay.replayActions : Stream<ViewerAction>
viewers over time viewerStates(last, actions) Replay.viewerStates(last: Int, actions: Stream[ViewerAction]): Stream[Viewer] Replay.viewerStates (last: int) (actions: Stream<ViewerAction>) : Stream<Viewer>
the whole viewer replayMain(recording) Replay.replayMain(recording: Recording): IO[Stream[IO[Unit]]] Replay.replayMain (recording: Recording) : IO<Stream<IO<unit>>>
entry point (given) js/src/replay-main.js (fetches the recording), page js/replay.html in scala/web/, page scala/replay.html in fsharp/web/, page fsharp/replay.html

Module 11 — Pong

JS files are in js/src/pong/, Scala files in scala/src/pong/ (objects PongModel, PongRules, PongView, PongApp in package snake.pong), F# files in fsharp/src/Pong/ (modules Snake.Pong.Model, Snake.Pong.Rules, Snake.Pong.View, Snake.Pong.App). In F#, the Pong unions are marked [<RequireQualifiedAccess>], so you always write PongStatus.Playing, Paddle.Up, PongAction.Frame; that keeps them apart from the snake's Playing, Up and Frame.

Concept JavaScript Scala F#
constants W, H, PCELL, PADDLE, PLAYER_X, CPU_X, WIN (model.js) PongModel.W, ... PongModel.WIN Pong.Model.W, ... Pong.Model.WIN
velocity vel: {dx, dy} Velocity(dx, dy) { Dx: int; Dy: int } (type Velocity)
held keys held: {up, down} Held(up, down) { Up: bool; Down: bool } (type Held)
score score: {player, cpu} Score(player, cpu) { Player: int; Cpu: int } (type Score)
status 'Playing', 'Over' enum PongStatus: PongStatus.Playing, PongStatus.Over PongStatus.Playing, PongStatus.Over
paddle keys 'Up', 'Down' enum Paddle: Paddle.Up, Paddle.Down Paddle.Up, Paddle.Down
match state {ball: {x, y}, vel, player, cpu, held, score, status, seed, frame} PongState(ball: Point, vel, player, cpu, held, score, status, seed: Long, frame) { Ball: Point; Vel; Player; Cpu; Held; Score; Status; Seed: uint32; Frame }
actions PongAction.Press(key), PongAction.Release(key), PongAction.Frame, PongAction.Restart; values {tag: 'Press', key}, ... enum PongAction: Press(key: Paddle), Release(key: Paddle), Frame, Restart PongAction.Press of Paddle, PongAction.Release of Paddle, PongAction.Frame, PongAction.Restart
fresh match initialPong(seed) (model.js) PongModel.initialPong(seed: Long): PongState Pong.Model.initialPong (seed: uint32) : PongState
keep in range clamp(lo, hi, x) (rules.js) PongRules.clamp(lo: Int, hi: Int, x: Int): Int Pong.Rules.clamp (lo: int) (hi: int) (x: int) : int
the update function pongUpdate(state, action) PongRules.pongUpdate(state: PongState, action: PongAction): PongState Pong.Rules.pongUpdate (state: PongState) (action: PongAction) : PongState
state to picture pongView(state) (view.js) PongView.pongView(state: PongState): Scene Pong.View.pongView (state: PongState) : Scene
ms per frame FRAME_MS_PONG = 30 (app.js) PongApp.FRAME_MS_PONG: Long Pong.App.FRAME_MS_PONG (30L)
key to action pongKey(isDown, key) → action or null PongApp.pongKey(isDown: Boolean, key: String): Option[PongAction] Pong.App.pongKey (isDown: bool) (key: string) : PongAction option
actions over time pongActions PongApp.pongActions: Stream[PongAction] Pong.App.pongActions : Stream<PongAction>
states over time pongStates(seed, actions) PongApp.pongStates(seed: Long, actions): Stream[PongState] Pong.App.pongStates (seed: uint32) (actions: Stream<PongAction>) : Stream<PongState>
the whole program pongMain (a value) PongApp.pongMain: IO[Stream[IO[Unit]]] Pong.App.pongMain () : IO<Stream<IO<unit>>>
entry point (given) js/src/pong/main.js, page js/pong.html PongMain in scala/web/Main.scala, page scala/pong.html fsharp/web/PongMain.fs, page fsharp/pong.html

Module 12 — property-based testing (bonus)

Concept JavaScript (js/src/check.js) Scala (scala/src/Check.scala) F# (fsharp/src/Check.fs)
a generator a Random<A> (Module 4) Random[A] Random<'a>
pick an element elementOf(xs) Check.elementOf(xs: List[A]): Random[A] Check.elementOf (xs: 'a list) : Random<'a>
n draws in a row listOf(gen, n) Check.listOf(gen, n): Random[List[A]] Check.listOf (n: int) gen : Random<'a list>
random actions (given) genActions(n) Check.genActions(n): Random[List[Action]] Check.genActions (n: int) : Random<Action list>
the report {ok: true, runs} or {ok: false, run, seed, value} CheckResult.Passed(runs), CheckResult.Failed(run, seed, value) Passed runs, Failed(run, seed, value)
check a property forAll(gen, prop, runs, seed) Check.forAll(gen, runs, seed)(prop) Check.forAll runs seed prop gen
one element removed removals(xs) Check.removals(xs): List[List[A]] Check.removals xs : 'a list list
shrink a failing list minimize(xs, fails) Check.minimize(xs)(fails) Check.minimize fails xs
broken invariants violations(state) → names Check.violations(state): List[String] Check.violations state : string list
frames between moves moveGaps(states) Check.moveGaps(states): List[Int] Check.moveGaps states : int list
a random snake genSnake(width, height, maxLen) Check.genSnake(width: Int, height: Int, maxLen: Int): Random[List[Point]] Check.genSnake (width: int) (height: int) (maxLen: int) : Random<Point list>
a random sane state (given) genState(width, height) Check.genState(width: Int, height: Int): Random[GameState] Check.genState (width: int) (height: int) : Random<GameState>
the step, as an oracle advanceSpec(state) Check.advanceSpec(state: GameState): GameState Check.advanceSpec (state: GameState) : GameState

Module 13 — Conway's Life

JS files are in js/src/life/, Scala files in scala/src/life/ (objects LifeModel, LifeRules, LifeView, LifeApp in package snake.life), F# files in fsharp/src/Life/ (modules Snake.Life.Model, Snake.Life.Rules, Snake.Life.View, Snake.Life.App; LifeAction is [<RequireQualifiedAccess>]). The click at the edge is in the Module 8 and 9 tables above.

Concept JavaScript Scala F#
constants LW = 40, LH = 30, LCELL = 16, STRIP = 24 (model.js) LifeModel.LW, ... LifeModel.STRIP Life.Model.LW, ... Life.Model.STRIP
world state {cells, running, seed, generation}; cells sorted row-major, no duplicates LifeState(cells: List[Point], running: Boolean, seed: Long, generation: Int) { Cells: Point list; Running: bool; Seed: uint32; Generation: int }
actions LifeAction.Toggle(cell), LifeAction.Frame, .TogglePause, .Step, .Clear, .Randomize; values {tag: 'Toggle', cell}, {tag: 'Frame'}, ... enum LifeAction: Frame, Toggle(cell: Point), TogglePause, Step, Clear, Randomize LifeAction.Frame, LifeAction.Toggle of Point, ...
fresh world initialLife(seed) LifeModel.initialLife(seed: Long): LifeState Life.Model.initialLife (seed: uint32) : LifeState
one generation lifeStep(cells) (rules.js) LifeRules.lifeStep(cells: List[Point]): List[Point] Life.Rules.lifeStep (cells: Point list) : Point list
the update function lifeUpdate(state, action) LifeRules.lifeUpdate(state: LifeState, action: LifeAction): LifeState Life.Rules.lifeUpdate (state: LifeState) (action: LifeAction) : LifeState
state to picture lifeView(state) (view.js) LifeView.lifeView(state: LifeState): Scene Life.View.lifeView (state: LifeState) : Scene
ms per generation GEN_MS = 150 (app.js) LifeApp.GEN_MS: Long Life.App.GEN_MS (150L)
key to action lifeKey(key) → action or null LifeApp.lifeKey(key: String): Option[LifeAction] Life.App.lifeKey (key: string) : LifeAction option
click to action lifeClick(x, y) → Toggle or null LifeApp.lifeClick(x: Int, y: Int): Option[LifeAction] Life.App.lifeClick (x: int) (y: int) : LifeAction option
actions over time lifeActions LifeApp.lifeActions: Stream[LifeAction] Life.App.lifeActions : Stream<LifeAction>
states over time lifeStates(seed, actions) LifeApp.lifeStates(seed: Long, actions: Stream[LifeAction]): Stream[LifeState] Life.App.lifeStates (seed: uint32) (actions: Stream<LifeAction>) : Stream<LifeState>
the whole program lifeMain (a value) LifeApp.lifeMain: IO[Stream[IO[Unit]]] Life.App.lifeMain () : IO<Stream<IO<unit>>>
entry point (given) js/src/life/main.js, page js/life.html in scala/web/, page scala/life.html in fsharp/web/, page fsharp/life.html

Tests and tools

Task JavaScript (cd js) Scala (cd scala) F# (cd fsharp)
one module's tests node --test "test/m03-*.test.js" or ./test.sh M03 ./test.sh M03 ./test.sh M03
all tests (with the purity audit) ./test.sh ./test.sh ./test.sh
test files test/m03-rules.test.js test/M03Rules.test.scala tests/M03Rules.fs
purity audit test/purity.test.js test/Purity.test.scala (PuritySuite) tests/Purity.fs
type checking (optional) npx -y -p typescript tsc -p js/jsconfig.json from the repository root, or VS Code as it is; types in js/src/types.d.ts (see Module 0) the compiler, on every test run the compiler, on every test run
build for the browser nothing to build ./build-web.sh (Scala.js, writes web/dist/) ./build-web.sh (Fable, writes web/dist/)
play node scripts/serve.mjs from the repository root, then http://localhost:8000/js/ (Pong: /js/pong.html, replay viewer: /js/replay.html, Life: /js/life.html) same server, http://localhost:8000/scala/ same server, http://localhost:8000/fsharp/

See also: Module 0 for setup, the method for the conversion recipe, the README for the module map, and MENTOR.md for AI-mentor mode.