# 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}` | `GameState(width, height, snake: List[Point], dir, nextDir, apple: Option[Point], seed: Long, score, status, frame)` | `{ Width; Height; Snake: Point list; Dir; NextDir; Apple: Point option; Seed: uint32; Score; Status; Frame }` | | 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` | | a number in `[0, n)` | `intBelow(n)` | `Random.intBelow(n: Int): Random[Int]` | `Random.intBelow (n: int) : Random` | In F#, do every `*` and `+` of the generator through `uint64` helpers so it also works in the browser (Module 4, "F# in the browser"). | 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` | ## 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'}` | `enum Op`: `Op.Draw(shape)`, `Op.RandomSeed` | `type Op = Draw of Shape \| RandomSeed` | | answers to operations | plain values: `undefined` for a draw, a number for a seed | `enum OpResult`: `OpResult.Done`, `OpResult.Seed(value: Long)` | `type OpResult = Done \| Seed of uint32` | | 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` | | 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 list) : IO` | | draw one shape | `draw(shape)` | `IO.draw(shape): IO[Unit]` | `IO.draw shape : IO` | | ask for a seed | `randomSeed` (a value) | `IO.randomSeed: IO[Long]` (a value) | `IO.randomSeed () : IO` (a function of unit) | | draw a scene | `drawScene(scene)` | `IO.drawScene(scene): IO[Unit]` | `IO.drawScene scene : IO` | | pure test interpreter | `trace(io, seed)` → `{result, ops}` | `IO.trace(io, seed): Traced[A]`, `Traced(result, ops)` | `IO.trace seed io : Traced<'a>`, `{ Result; Ops }` | ## 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}` | `enum Event`: `Event.Time(t: Long)`, `Event.KeyDown(key)`, `Event.KeyUp(key)` | `type Event = Time of int64 \| KeyDown of string \| KeyUp of string` | | 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` | | a clock | `interval(ms)` | `Stream.interval(ms: Long): Stream[Long]` | `Stream.interval (ms: int64) : Stream` | | 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` | ## 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`) | | actions, classic clock | `classicActions` | `App.classicActions: Stream[Action]` | `App.classicActions : Stream` | | actions, frame clock (m10) | `speedActions` | `App.speedActions: Stream[Action]` | `App.speedActions : Stream` | | states over time | `states(seed, actions)` | `App.states(seed: Long, actions): Stream[GameState]` | `App.states (seed: uint32) (actions: Stream) : Stream` | | draw one state | `render(state)` | `App.render(state): IO[Unit]` | `App.render (state: GameState) : IO` | | the whole program | `main` (a value) | `App.main: IO[Stream[IO[Unit]]]` (a value) | `App.main () : IO>>` (a function of unit) | | 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)` | `trait Platform` with the same method names; `randomSeed(): Long`, `startClock(onTime: Long => Unit)` | `type Platform` record of functions: `FillRect`, `FillText`, `RandomSeed: unit -> uint32`, `StartClock: (int64 -> unit) -> unit`, `OnKeyDown`, `OnKeyUp` | | interpret an IO | `runIO(io, platform)` (`runtime.js`) | `Runtime.runIO(io, p)` | `Runtime.runIO p io` | | run the program | `runApp(main, platform)` | `Runtime.runApp(main, p)` | `Runtime.runApp p main` | | browser platform | `browserPlatform(canvas, win)` (`browser.js`, you write it) | `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 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 `[]`, 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` | | states over time | `pongStates(seed, actions)` | `PongApp.pongStates(seed: Long, actions): Stream[PongState]` | `Pong.App.pongStates (seed: uint32) (actions: Stream) : Stream` | | the whole program | `pongMain` (a value) | `PongApp.pongMain: IO[Stream[IO[Unit]]]` | `Pong.App.pongMain () : IO>>` | | 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` | ## 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` | | 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 (Pong: `/js/pong.html`) | same server, | same server, | See also: [Module 0](00-setup.md) for setup, [the method](method.md) for the conversion recipe, the [README](../README.md) for the module map, and [MENTOR.md](../MENTOR.md) for AI-mentor mode.