# Module 7 — The IO type
> **Time:** ~5 h (take a break halfway; this is the deepest module of the course) · **You'll edit:** JS `js/src/io.js` · Scala `scala/src/IO.scala` · F# `fsharp/src/IO.fs`
> **Tests:** JS `cd js && node --test "test/m07-*.test.js"` · Scala `cd scala && ./test.sh M07` · F# `cd fsharp && ./test.sh M07`
> **Prerequisites:** Modules 0–6, especially Module 4 (the state-passing pattern, `pure`/`map`/`flatMap` on `Random`) and Module 6 (`Scene`)
## Why this module
Here is a question that sounds like a contradiction: **what does it mean for a program that draws on the screen to be pure?**
A pure function, as you saw in Module 1, gives the same result for the same arguments and does nothing else. Drawing *is* "something else". If `drawSquare()` paints a square, then calling it twice paints twice; you can't replace the call with its result (`undefined`) without changing what the program does. Asking the browser for a random seed is worse: the same call gives a different answer each time. So it seems a game must be impure somewhere in the middle, wherever it draws or asks the outside world for something.
Module 6 already showed the way out for one half of the problem. `view` doesn't draw; it returns a `Scene`, a *description* of a drawing. This module takes that idea to its conclusion: **every effect becomes a value that describes it**. "Draw this rectangle" becomes a value. "Ask for a random seed, and then, with the answer, do this" becomes a value. A whole program becomes a value. Building those values is pure. Something, at the very edge, still has to *perform* the description — but that will be one small function (Module 9), and everything you write before it stays pure and testable.
The type of such descriptions is called **`IO`**. You will build it from scratch, understand why it has exactly the shape it has, write a pure interpreter for it so you can test programs without a screen, and check that it obeys the three laws that make it a **monad**.
## Learning objectives
By the end of this module you can:
- **explain** the difference between performing an effect and describing it, and why descriptions keep a program referentially transparent;
- **explain** why an `IO` needs a *continuation* `k` and cannot be a plain list of operations;
- **implement** `pure`, `effect`, `map`, `flatMap`, `sequence`, `draw`, `randomSeed`, `drawScene` and a pure interpreter `trace`;
- **state** the three monad laws and **explain** how the tests check them using an interpreter;
- **predict** when a function given to `map`/`flatMap` runs: when the program is built, or when it is interpreted;
- **compare** our `IO` with Haskell's `IO`, cats-effect, ZIO and free monads.
## Concepts
### 1. Performing versus describing
Recall referential transparency from Module 1: an expression is referentially transparent when you can replace it by its value without changing the program. Try it on these two snippets:
```js
const x = Math.random()
const pair1 = [x, x]
const pair2 = [Math.random(), Math.random()]
```
If `Math.random()` were a value you could substitute, `pair1` and `pair2` would be the same. They are not: `pair1` has two equal numbers, `pair2` almost surely two different ones. `Math.random()` is not an expression that *has* a value; it is an *action* that *produces* one when performed.
Now compare a different design. Suppose `randomSeed` is not an action but a **value that describes the action** "ask the outside world for a seed":
```js
const randomSeed = { tag: 'Effect', op: { tag: 'RandomSeed' }, /* ... we'll see what goes here ... */ }
const program1 = [randomSeed, randomSeed]
```
This `randomSeed` is a plain object. Writing it twice or naming it once makes no difference at all — you can substitute freely. It does nothing until someone *interprets* it. That is the trick of the whole module, and it is also, essentially, how Haskell has been pure since the 1990s ([A Gentle Introduction to Haskell: Input/Output](https://www.haskell.org/tutorial/io.html)).
You saw the same move twice already:
| Module | The "doing" version | The "describing" version |
|---|---|---|
| 4 | `Math.random()` mutates a hidden seed | `Random = seed → [A, seed]`, a function you can pass around |
| 6 | `draw(ctx, state)` paints | `view(state)` returns a `Scene` |
| 7 | `ctx.fillRect(...)`, `crypto.getRandomValues(...)` | `IO`, a value describing a program that produces an `A` |
### 2. First attempt: a program is a list of operations
Let us design the description step by step. The simplest idea: a program is a list of **operations** (we'll call them `Op`s), and an interpreter performs them one after another. We need two operations for the whole course:
**JavaScript**
```js
// An Op is plain data:
// { tag: 'Draw', shape } draw a Rect or Text from Module 6 (answer: nothing, undefined)
// { tag: 'RandomSeed' } ask the outside world for a seed (answer: a uint32 number)
```
**Scala**
```scala
// Already in scala/src/IO.scala
enum Op:
case Draw(shape: Shape)
case RandomSeed
enum OpResult: // what the outside world answers
case Done // ... to a Draw
case Seed(value: Long) // ... to a RandomSeed
```
**F#**
```fsharp
// Already in fsharp/src/IO.fs
type Op =
| Draw of Shape
| RandomSeed
type OpResult =
| Done
| Seed of uint32
```
**Language note.** The Scala and F# tracks need an explicit `OpResult` type, because an interpreter must hand back *some* typed answer for any `Op`. JS is untyped, so the answer to `Draw` is just `undefined` and the answer to `RandomSeed` is just a number.
A list-of-ops program and its interpreter would look like this (illustration only, not part of the exercises):
```js
// A program as a list of ops, and an interpreter that performs them.
const program = [{ tag: 'Draw', shape: background }, { tag: 'Draw', shape: score }]
const runAll = (ops, platform) => ops.forEach((op) => perform(op, platform))
```
> **From everyday programming:** you have met this idea under other names. In the [command pattern](https://refactoring.guru/design-patterns/command), a request such as "save the document" becomes an object that can be stored, queued, logged or undone, and some other object executes it later. A job queue holds descriptions of work ("resize image 42") that a worker performs when it gets to them. A recorded macro in an editor is a list of keystrokes that does nothing until you replay it. In each case, *describing* the work and *doing* it are separated, and something else — the executor, the worker, the replay button — decides when and how the work happens. An `Op` is a command object; an interpreter is the executor.
For drawing a scene, this is enough: `drawScene` would be `scene.map(shape => ({tag:'Draw', shape}))`. But now try to write this program:
> Ask for a random seed. Then start a new game with that seed, and draw it.
The shapes to draw depend on the seed, and the seed only exists *after* the first operation has been performed. A list must be complete before anybody performs it, so there is nowhere to put "and then, *with the answer*, decide what to do next". The list-of-ops design can describe programs whose steps are fixed in advance, and nothing else.
### 3. Second attempt: an operation and a continuation
The fix is to pair each operation with **a function that receives the answer and returns the rest of the program**:
```
Effect( op = RandomSeed,
k = seed => )
```
That function is called the **continuation**, traditionally named `k`. "Continuation" just means "what to do next"; you have used them every time you passed a callback (`button.onclick = () => ...`), and the idea has a long history under the name [continuation-passing style](https://en.wikipedia.org/wiki/Continuation-passing_style).
When the program is finished there is no operation left, only a final value. So a program is one of two things:
- **`Pure(value)`** — done; the program's result is `value`;
- **`Effect(op, k)`** — perform `op`; give its answer to `k`; `k` returns the **rest of the program** (another `IO`).
That's the whole type. Here it is in the three tracks (this definition is given to you in the stub files):
**JavaScript**
```js
// IO =
// { tag: 'Pure', value } value : A
// { tag: 'Effect', op, k } op : Op, k : (answer) => IO
```
**Scala**
```scala
enum IO[+A]:
case Pure(a: A)
case Effect(op: Op, k: OpResult => IO[A])
```
**F#**
```fsharp
type IO<'a> =
| Pure of 'a
| Effect of Op * (OpResult -> IO<'a>)
```
> **From everyday programming:** compare a linked list. Each node holds a value and a `next` pointer to the rest of the list, and the last node has no `next`. An `IO` has the same recursive shape — `Effect` nodes chained together, ending in a `Pure` — with one difference: the `next` pointer is replaced by a function, so you can only get the rest of the program by supplying an answer. A linked list is a chain of values; an `IO` is a chain of *questions*. Think of a phone menu: "press 1 for sales, 2 for support" — which question comes next depends on what you answered.
Let us write the program from the previous section *by hand*, with no helpers at all. (JS shown; it is the same tree in every language.)
```js
// "Ask for a seed; then draw a square whose x depends on the seed; then finish with the seed."
const program = {
tag: 'Effect',
op: { tag: 'RandomSeed' },
k: (seed) => ({
tag: 'Effect',
op: { tag: 'Draw', shape: { tag: 'Rect', x: seed % 20, y: 0, w: 1, h: 1, color: '#ffffff' } },
k: (_) => ({ tag: 'Pure', value: seed }),
}),
}
```
Drawn as a picture, the program is a chain where each link waits for an answer:
```
Effect(RandomSeed) ──answer: seed──▶ Effect(Draw(Rect at x = seed % 20)) ──answer: (none)──▶ Pure(seed)
```
Stop and check two things:
1. **Building `program` performed nothing.** No seed was asked for; no square was drawn. `program` is an object, and the functions inside it have not been called.
2. **The second operation depends on the first answer.** That dependency is written as an ordinary JS function. This is the expressive power the list did not have.
### 4. Interpreting a program by hand
An **interpreter** is a function that walks this structure: if it sees `Pure`, it's done; if it sees `Effect(op, k)`, it performs `op` somehow, calls `k` with the answer, and continues with whatever `k` returns.
The interesting phrase is "performs `op` *somehow*". Who decides what "performing" means? The interpreter does. That gives us the freedom to write two very different interpreters for the same programs:
- **`trace(io, seed)`** (this module): a *pure* interpreter for tests. It doesn't draw anything. It answers every `RandomSeed` with the fixed `seed` you give it, answers every `Draw` with "done", and **records** the list of operations it saw. It returns `{ result, ops }`.
- **`runIO(io, platform)`** (Module 9): the real interpreter, which calls the canvas and the browser's random generator.
Let us run `trace(program, 7)` by hand. Fill in the table yourself before reading the last row:
| Step | Current program | Operation performed | Answer given | `ops` so far |
|---|---|---|---|---|
| 1 | `Effect(RandomSeed, k1)` | `RandomSeed` | `7` | `[RandomSeed]` |
| 2 | `k1(7)` = `Effect(Draw(Rect x=7), k2)` | `Draw(Rect x=7)` | `undefined` (Scala/F#: `Done`) | `[RandomSeed, Draw(Rect x=7)]` |
| 3 | `k2(undefined)` = `Pure(7)` | — | — | finished: `{ result: 7, ops: [RandomSeed, Draw(Rect x=7)] }` |
Now ask: *is `trace` a pure function?* Same `io`, same `seed` → same `{result, ops}`, every time; nothing outside is touched. Yes. So programs that "use randomness and draw on the screen" can be tested with `deepStrictEqual`, the same way you have tested everything so far. This is the single most important consequence of the module.
> **From everyday programming:** `trace` plays the role a mock or fake plays in a unit test. When code talks to a database through an interface, you can inject an in-memory fake in the tests and the real database in production — that is dependency injection. Here the program doesn't even hold a reference to the thing it talks to: it is pure data, and the interpreter you hand it to *is* the injected dependency. `trace` is the fake that records every call; `runIO` is the real implementation.
You will write `trace` by **recursion** (no loops, no mutation): the ops of `Effect(op, k)` are `op` followed by the ops of whatever `k(answer)` traces to. Walking a structure like this, node by node, is the same kind of work as summing a linked list recursively, except that to get from one node to the next you must supply an answer.
### 5. Writing programs comfortably: `pure` and `effect`
Nobody wants to write nested objects like `program` above. We need small building blocks and ways to glue them. The two simplest:
- **`pure(a)`** — the program that does nothing and produces `a`. It is just `Pure(a)`.
- **`effect(op)`** — the program that performs exactly one `op` and produces its answer. What should its continuation be? It must take the answer and return "the rest of the program", and the rest of the program is "nothing more; the result is the answer". Which of our building blocks is exactly "take a value, return a finished program with that value"? (The docs in the stub say it outright: `Effect(op, pure)`.)
With `effect` you can build the two operations the game needs:
- **`draw(shape)`** — one `Draw` operation. In JS the answer is already `undefined`, so `draw(shape)` can be `effect(...)` directly. In Scala/F# `effect` gives an `IO[OpResult]`, but `draw` promises `IO[Unit]`: you'll need to turn the `Done` into `()`.
- **`randomSeed`** — one `RandomSeed` operation, producing the seed. In Scala/F# you must turn `Seed(value)` into `value`. What about `Done`? An interpreter that answers a `RandomSeed` with `Done` is broken; the honest thing to do is fail loudly (throw an exception with a clear message). That case cannot happen with a correct interpreter.
Notice that **`randomSeed` is a value, not a function**. In JS you write `randomSeed`, not `randomSeed()`. (F# is the exception in spelling only: there it is `randomSeed ()`, a function of unit, because F# evaluates module-level values as soon as the module is first used, and a failing stub would then break every test. Every call returns the same description, so it is still a value in every sense that matters.) Why does that make sense? Because the *description* "ask for a seed" is always the same description. Using it twice in a program asks twice when interpreted, exactly as you would expect — but the value itself never changes. (In the JS stub it is a strange-looking object with a getter, only so that the stub fails loudly; replace it with a plain value.)
### 6. `map`: changing the result without running anything
Suppose you have `randomSeed : IO` and you want `IO`: "ask for a seed, and the result is `newGame(seed)`". That's `map(randomSeed, newGame)`.
What must `map(io, f)` do? Think case by case, and think about **when** `f` may run:
- If `io` is `Pure(a)`, the value is right there. The result is a finished program whose value is `f(a)`.
- If `io` is `Effect(op, k)`, there is *no value yet*. It will only exist when an interpreter performs `op` and calls `k`. So `map` can't call `f` now. The new program must perform the same `op`, and its continuation must take the answer, compute the rest of the original program with `k`, and then **apply `f` to whatever that rest eventually produces**. That last part is itself a `map` — on a smaller program.
That second case is the heart of the matter: `map` doesn't transform a value; it builds a *new description* that will transform the value once it exists. The tests check this directly:
```js
test('m07 building map(io, throwingF) does not throw', ...) // building must not call f
test('m07 tracing map(io, throwingF) runs f and throws', ...) // interpreting must call f
```
> **From everyday programming:** on an array, `[1, 2, 3].map(f)` applies `f` to every element and gives you a new array; the values are there, so `f` runs at once. A closer analogue is a JS promise (or a C# `Task`, or a Java `CompletableFuture`): in `fetch(url).then((response) => response.status)`, the callback can't run until the response arrives, so `.then` registers it to run later and immediately hands you a new promise for the status. Our `map` does the same thing, with one difference: a promise represents work that has *already started*, whereas an `IO` is a description that nothing has started yet. `map` on an `IO` registers `f` inside plain data, with no threads and no network involved.
**Language note.** JS functions are data-first, `map(io, f)`. Scala uses a method, `io.map(f)`. F# uses a data-last module function, `IO.map f io`, so it works with the pipe: `io |> IO.map f`.
### 7. `flatMap`: "and then"
`map` lets the *result* depend on an earlier answer. But we need more: the *next operations* must depend on an earlier answer ("ask for a seed, *then draw the game made from it*"). That's `flatMap(io, f)`, where `f` returns not a value but another program:
```
flatMap(io: IO, f: A → IO) : IO
```
Read it as "run `io`; with its result `a`, continue with the program `f(a)`". It's the semicolon of our little language. Again, case by case:
- `Pure(a)`: the first program is already finished with `a`, so the combined program *is* `f(a)`.
- `Effect(op, k)`: perform the same `op`; the new continuation takes the answer, gets the rest of the original program from `k`, and then **continues that rest with `f`**. Again, the "and then continue with `f`" is itself a `flatMap` on a smaller program.
A picture helps. `flatMap` takes the chain of `io` and grafts `f` onto its end — at the place where `io` would have said `Pure(a)`:
```
io: Effect(op1) ─▶ Effect(op2) ─▶ Pure(a)
flatMap(io, f): Effect(op1) ─▶ Effect(op2) ─▶ f(a) = Effect(op3) ─▶ Pure(b)
```
Two observations to make you comfortable:
1. Once you have `flatMap` and `pure`, you get `map` for free: "transform the result with `f`" is the same as "and then finish with `f(a)`". Can you write `map` in one line using `flatMap` and `pure`? (This is a classic exercise; it is also a nice way to check your `flatMap`.)
2. You have seen this exact pair before. In Module 4, `Random` had `pure`, `map` and `flatMap` with the same shapes and the same meaning ("then, with the value, continue"). This is not a coincidence; it's a pattern with a name, which you will meet properly in section 10.
> **From everyday programming:** the name `flatMap` appears on many types, always with the shape "apply a function that returns another container, then flatten". On a JS array, `[1, 2].flatMap((n) => [n, n * 10])` is `[1, 10, 2, 20]`: for each element, an array; concatenate them. On promises the same idea is spelled `.then` with a callback that returns another promise: if `loadUser(id)` and `loadOrders(user)` both return promises, `loadUser(id).then((user) => loadOrders(user))` means "when the user arrives, start the next request with it, and give me a promise of the orders" — a promise of orders, not a promise of a promise. On `IO` it means "when the answer arrives, continue with this program".
>
> **Scala-track syntax note:** in the Scala track, `flatMap` is what the `for`-comprehension syntax translates to; section 11 shows it.
**Language note.** In F# this function is called **`bind`** (`IO.bind f io`), which is the traditional name in ML and Haskell (`>>=`). Same function, different name.
### 8. `sequence` and `drawScene`
To draw a whole scene we need to run many `IO` programs one after another. `sequence(ios)` builds that combined program, left to right; `sequence([])` is a program that does nothing and produces unit. Then
```
drawScene(scene) = sequence(scene.map(draw))
```
and every `Scene` from Module 6 becomes a program that draws it. `trace(drawScene(view(s)), 0).ops` is then exactly `view(s)` wrapped in `Draw` ops — which is what Module 9's `render` test checks.
How should you build `sequence`? It's a fold: combine programs pairwise with `flatMap` ("run this one, then the rest"). There are two ways to fold, and they produce differently *shaped* programs:
```
right-nested: flatMap(io1, _ => flatMap(io2, _ => flatMap(io3, _ => pure())))
left-nested: flatMap(flatMap(flatMap(pure(), _ => io1), _ => io2), _ => io3)
```
Both mean the same thing (the associativity law below guarantees it), and both pass this module's tests. But they don't cost the same. Keep the question "which one should I build?" in mind; section 12 answers it, and Module 9 has a test that cares.
### 9. When does `f` run? An honest note about eagerness
You should now be able to predict exactly when the function you pass to `flatMap` runs. Look at the `Pure` case again: `flatMap(Pure(a), f)` is `f(a)` — and our implementation computes `f(a)` **right away, while building the program**, not later during interpretation.
Is that a problem? In a pure world, no. If `f` is pure, calling it now or later gives the same result and has no other effect; nobody can tell the difference. Building `flatMap(pure(3), n => draw(...))` doesn't draw anything either way, because `f` only *returns a description* of drawing.
There are exactly two ways to notice the difference, and you should know them:
- **Exceptions.** `flatMap(pure(1), _ => { throw new Error('boom') })` throws while *building*, whereas `flatMap(randomSeed, _ => { throw ... })` only throws when *interpreted*.
- **Non-termination or very expensive work.** If `f` loops forever or takes a minute, building the program hangs or takes a minute.
Both are, in a sense, impurities in `f`, and our `f`s won't have them. Production libraries do care: cats-effect, ZIO and Haskell's `IO` add a case like `Suspend(() => io)` / `Delay` so that even the first step can be postponed until interpretation. Our tiny `IO` doesn't need it, and it is good to understand *why* it doesn't.
### 10. The monad laws
A type with `pure` and `flatMap` that satisfies three equations is called a **monad**. The equations are not bureaucracy; each one is a refactoring you want to be allowed to do without changing what your program does.
Write `≡` for "behaves the same". For any value `a`, any program `m`, and any functions `f`, `g` that return programs:
1. **Left identity:** `flatMap(pure(a), f) ≡ f(a)`
*"Wrapping a value and immediately continuing with it is the same as continuing with it directly."*
2. **Right identity:** `flatMap(m, pure) ≡ m`
*"Continuing with 'return it unchanged' adds nothing."*
3. **Associativity:** `flatMap(flatMap(m, f), g) ≡ flatMap(m, x => flatMap(f(x), g))`
*"It doesn't matter how you group a chain of 'and then's."* This is what lets you extract any part of a sequence into a helper function.
In Scala and F# syntax:
**Scala**
```scala
IO.pure(a).flatMap(f) ≡ f(a)
m.flatMap(IO.pure) ≡ m
m.flatMap(f).flatMap(g) ≡ m.flatMap(x => f(x).flatMap(g))
```
**F#**
```fsharp
IO.pure a |> IO.bind f ≡ f a
m |> IO.bind IO.pure ≡ m
m |> IO.bind f |> IO.bind g ≡ m |> IO.bind (fun x -> f x |> IO.bind g)
```
**How can a test check "behaves the same"?** You can't compare two `IO` values with `==`: they contain functions, and functions can't be compared. But you *can* compare what they do under an interpreter! Two programs that produce the same result and perform the same operations, under the same answers, are indistinguishable to anyone using them. So the tests check:
```js
assert.deepStrictEqual(trace(flatMap(pure(a), f), 5), trace(f(a), 5))
```
for a few programs (`pure(4)`, `randomSeed`, and a draw followed by `randomSeed`). This is called **observational equality**: equal as far as any observer (here: the interpreter) can tell. It is also a lesson in why the pure interpreter is valuable: without it, you could not even *state* these tests.
> **From everyday programming:** you rely on laws like these whenever you refactor. Associativity is the reason you can take three consecutive steps of a function, move the last two into a helper, and call the helper — the grouping changed, the behaviour didn't. It is the same property that lets you write `a + b + c` without parentheses. JS arrays obey all three laws too: with `pure(a) = [a]`, `[a].flatMap(f)` equals `f(a)`, and `xs.flatMap((x) => [x])` equals `xs`. Try the associativity law on a small array in the console.
>
> **Scala-track syntax note:** the Scala compiler rewrites a `for`-comprehension into nested `flatMap`s in one particular grouping; associativity is what makes that grouping irrelevant. F#'s computation expressions rely on the same law.
### 11. Syntax that hides the plumbing
Nested `flatMap`s become hard to read quickly. Scala and F# offer syntax for exactly this pattern; JS doesn't (but see the note).
**Scala** — the Scala track has a `for`-comprehension for this. Each line `x <- io` means "run `io`, call its result `x`, and continue with the lines below"; `yield` gives the final result. The compiler turns the block into nested `flatMap`s with a `map` at the end, so because `IO` has `map` and `flatMap`, it works in a `for`-comprehension with no extra work:
```scala
val program: IO[Long] =
for
seed <- IO.randomSeed
_ <- IO.drawScene(View.view(Spawn.newGame(seed)))
yield seed
// desugars to: IO.randomSeed.flatMap(seed => IO.drawScene(...).map(_ => seed))
```
**F#** — F# has *computation expressions*. You enable them by writing a small builder object that tells the compiler which functions mean "bind" and "return". This is optional and not tested; try it after the exercises:
```fsharp
type IOBuilder() =
member _.Bind(io, f) = IO.bind f io
member _.Return(a) = IO.pure a
member _.ReturnFrom(io) = io
let io = IOBuilder()
let program : IO =
io {
let! seed = IO.randomSeed ()
do! IO.drawScene (View.view (Spawn.newGame seed))
return seed
}
```
**JavaScript** has no such syntax. (Generators can imitate it — libraries such as redux-saga do this — but that's beyond our scope.) We'll keep our JS programs small enough that plain `flatMap` reads well.
Read the two snippets above and say what they would *do* if interpreted by `trace(…, 42)`: what is the result, and what is the first element of `ops`?
### 12. Stack safety (why the shape of the tree matters)
Our `IO` is made of nested functions, and calling nested functions uses the call stack. Two places can blow the stack if a program is very long (tens of thousands of operations):
- **The interpreter.** `trace` is written by recursion, one level per operation. That's fine for tests (a few hundred ops). The real interpreter in Module 9, `runIO`, must handle a full game for hours and is written as a **loop** instead (or, in Scala, a `@tailrec` function, which the compiler turns into a loop).
- **The continuations themselves.** Look again at what `flatMap` does to an `Effect`: its new continuation calls the old `k` and then calls `flatMap` again on the result. If you nest `flatMap` on the *left* — `flatMap(flatMap(flatMap(…)))` — then the first continuation, when called, must unwind every one of those levels at once before it can return the next step. A left-nested chain of 100 000 `flatMap`s overflows the stack *even if the interpreter is a loop*.
That is the answer to the question from section 8: **build `sequence` right-nested** (fold from the right), so each continuation does a constant amount of work before handing back the next `Effect`. Module 9 has a test that runs `sequence` of 100 000 draws through `runIO`; a left-nested `sequence` will fail it with a stack overflow.
Production libraries solve this once and for all with a technique called **trampolining**: they represent `flatMap` itself as a data constructor (`FlatMap(io, f)`) and the interpreter re-associates left-nested chains on the fly, in a loop, so no shape of program can overflow the stack. Rúnar Bjarnason's paper ["Stackless Scala With Free Monads"](http://blog.higher-order.com/assets/trampolines.pdf) explains it beautifully; see also [Trampoline (computing)](https://en.wikipedia.org/wiki/Trampoline_%28computing%29). We keep the simpler representation and the rule "fold right".
### 13. What we have built, and its family name
Step back and look at the type: a program is either *done*, or *one instruction plus a function from the instruction's answer to the rest of the program*. The instructions (`Op`) are a separate, ordinary data type; the `IO` structure doesn't care what they are. This shape is known as a **free monad** (more precisely, a close cousin called the "operational" or "freer" monad): it gives you `pure`/`flatMap` and the monad laws *for free* on top of any set of instructions, and leaves the meaning of the instructions entirely to the interpreter. Gabriel Gonzalez's ["You Could Have Invented Free Monads"](https://www.haskellforall.com/2012/06/you-could-have-invented-free-monads.html) tells the same story from Haskell.
Consequences you will use in the rest of the course:
- **Adding an effect is adding an `Op`.** Want to play a sound, or save a high score? Add `{tag:'PlaySound', name}` or `{tag:'Save', key, value}` and teach the interpreters what to do with it (you'll think through exactly this in Module 10).
- **Many interpreters.** `trace` for tests, `runIO` for the browser, and you could write a third that logs every op, or one that answers `RandomSeed` from a recorded file to replay a bug — the way you would swap in a different implementation of an interface with dependency injection, except that here nothing needs to be injected into the program at all.
## Exercises
> **About test names.** The test titles quoted under "Proved by" are the JavaScript ones. The Scala and F# suites test the same things one-to-one, but their titles use each track's vocabulary: "events" instead of "inputs", `None` instead of `null`, `()` instead of `undefined`, `bind`/`choose` instead of `flatMap`/`filterMap`, `IO.map throwingF io` instead of `map(io, throwingF)`, and so on. Failing tests are reported by name when you run the module's tests; to read all titles, open `scala/test/M07IO.test.scala` or `fsharp/tests/M07IO.fs`. Fixture-based tests have the same case names in every track.
All exercises are in the one file per track. Write them in the order below; each one uses the previous ones. Remember the purity rules: no `let`, no loops with mutation, no `push` in JS; no `var`/`mutable`. (`trace` is recursive.)
### Exercise 7.1 — `pure` and `effect`
**JavaScript**
```js
export const pure = (a) => /* IO */
export const effect = (op) => /* IO: Effect(op, pure) */
```
**Scala**
```scala
object IO:
def pure[A](a: A): IO[A] = ???
def effect(op: Op): IO[OpResult] = ???
```
**F#**
```fsharp
// module Snake.IO
let pure (a: 'a) : IO<'a> = failwith "TODO m07"
let effect (op: Op) : IO = failwith "TODO m07"
```
**Proved by:** `effect(op) is an Effect whose continuation is pure` (it calls the continuation with an answer and compares with `pure(answer)`).
### Exercise 7.2 — `map` and `flatMap`
**JavaScript**
```js
export const map = (io, f) => /* IO */
export const flatMap = (io, f) => /* IO */
```
**Scala** (methods on the `IO` enum)
```scala
enum IO[+A]:
...
def map[B](f: A => B): IO[B] = ???
def flatMap[B](f: A => IO[B]): IO[B] = ???
```
**F#** (data-last; `bind` is F#'s name for `flatMap`)
```fsharp
let map (f: 'a -> 'b) (io: IO<'a>) : IO<'b> = failwith "TODO m07"
let bind (f: 'a -> IO<'b>) (io: IO<'a>) : IO<'b> = failwith "TODO m07"
```
**Proved by:** `map transforms the result without adding ops`, `flatMap passes the result of the first IO to the second`, `building map(io, throwingF) does not throw`, `tracing map(io, throwingF) runs f and throws`, and the nine monad-law tests (`left identity holds for …`, `right identity holds for …`, `associativity holds for …`). The law tests need `trace` (7.4) to run, so expect them to stay red until then.
**Watch out for:** calling `f` in the `Effect` case while building. If your `map` ever calls `f` before an interpreter supplies an answer, the "does not throw" test catches it. In Scala, `flatMap` on the enum is a method, so you will `this match { ... }`.
### Exercise 7.3 — `sequence`, `draw`, `randomSeed`, `drawScene`
**JavaScript**
```js
export const sequence = (ios) => /* IO, left to right */
export const draw = (shape) => /* IO */
export const randomSeed = /* IO: a value, not a function */
export const drawScene = (scene) => /* sequence(scene.map(draw)) */
```
**Scala**
```scala
def sequence(ios: List[IO[Unit]]): IO[Unit] = ???
def draw(shape: Shape): IO[Unit] = ???
lazy val randomSeed: IO[Long] = ???
def drawScene(scene: Scene): IO[Unit] = ???
```
**F#**
```fsharp
let sequence (ios: IO list) : IO = failwith "TODO m07"
let draw (shape: Shape) : IO = failwith "TODO m07"
let randomSeed () : IO = failwith "TODO m07"
let drawScene (scene: Scene) : IO = failwith "TODO m07"
```
**Proved by:** `randomSeed is answered with the seed given to trace`, `randomSeed records a RandomSeed op`, `draw records a Draw op and produces undefined` (Scala/F#: `()`), `drawScene draws the shapes in order`, `drawScene of an empty scene has no ops`, `sequence of nothing has no ops`, `sequence runs effects left to right`.
**Watch out for:** building `sequence` *right-nested* (section 12). The m07 tests won't notice a left-nested one, but Module 9's 100 000-draw test will.
### Exercise 7.4 — `trace`, the pure interpreter
**JavaScript**
```js
export const trace = (io, seed) => /* { result, ops } */
```
**Scala**
```scala
final case class Traced[A](result: A, ops: List[Op])
def trace[A](io: IO[A], seed: Long): Traced[A] = ???
```
**F#**
```fsharp
type Traced<'a> = { Result: 'a; Ops: Op list }
let trace (seed: uint32) (io: IO<'a>) : Traced<'a> = failwith "TODO m07"
```
Answer every `RandomSeed` with the seed (`Seed(seed)` in Scala/F#), every `Draw` with unit (`Done`), and collect the ops in the order they were performed. Use recursion, not mutation.
**Proved by:** `trace of pure(3) has result 3 and no ops`, and every other test in the module (they all observe programs through `trace`).
**Run (all of Module 7):**
- JS: `cd js && node --test "test/m07-*.test.js"`
- Scala: `cd scala && ./test.sh M07`
- F#: `cd fsharp && ./test.sh M07`
## Hints
Exercise 7.1 — nudge
`pure` builds the "finished" case. `effect` builds the other case: which `op`? And which function, among those you've already written, takes an answer and returns "a finished program whose result is that answer"?
Exercise 7.1 — near-solution (try the others first)
```js
export const pure = (a) => ({ tag: 'Pure', value: /* ? */ })
export const effect = (op) => ({ tag: 'Effect', op, k: /* a function you just wrote */ })
```
In Scala, `IO.Pure(a)` and `IO.Effect(op, /* ? */)`; in F#, `Pure a` and `Effect (op, (* ? *))`.
Exercise 7.2 — nudge
Do `flatMap` first; `map` is easier afterwards. Split on the two cases of `io`. In the `Pure` case you have the value: what do you do with `f`? In the `Effect` case you have `op` and `k` but no value: what must the *new* `Effect` contain? Its `op` is the same. Its continuation receives an answer `r` … what can you compute from `r` with `k`, and what must happen to that afterwards?
Exercise 7.2 — stronger hint
For `Effect(op, k)`, `k(r)` is "the rest of the original program". You want "the rest of the original program, *and then* `f`". You're writing the function that means "and then `f`" right now — call it recursively on `k(r)`. The recursion happens *inside* the new continuation, so it only runs when an interpreter calls it.
For `map`, use `flatMap` and `pure`: "and then finish with `f` of the value".
Exercise 7.2 — near-solution (try the others first)
**JavaScript**
```js
export const flatMap = (io, f) =>
io.tag === 'Pure'
? /* ? */
: { tag: 'Effect', op: io.op, k: (r) => /* ? uses io.k, r, flatMap and f */ }
export const map = (io, f) => flatMap(io, (a) => /* ? */)
```
**Scala**
```scala
def flatMap[B](f: A => IO[B]): IO[B] = this match
case Pure(a) => /* ? */
case Effect(op, k) => Effect(op, r => /* ? */)
def map[B](f: A => B): IO[B] = flatMap(a => /* ? */)
```
**F#**
```fsharp
let rec bind (f: 'a -> IO<'b>) (io: IO<'a>) : IO<'b> =
match io with
| Pure a -> (* ? *)
| Effect (op, k) -> Effect (op, fun r -> (* ? *))
let map (f: 'a -> 'b) (io: IO<'a>) : IO<'b> = io |> bind (fun a -> (* ? *))
```
Exercise 7.3 — nudge
`draw` and `randomSeed` are one `effect` each (plus, in Scala/F#, a `map` to turn the `OpResult` into the right type). `sequence` is a fold over the list that starts from "the program that does nothing" and glues programs with `flatMap`, ignoring the unit results. Which fold keeps the tree right-nested (section 12)?
Exercise 7.3 — near-solution (try the others first)
**JavaScript**
```js
export const sequence = (ios) => ios.reduceRight((rest, io) => flatMap(/* ? */, () => /* ? */), /* ? */)
export const draw = (shape) => effect({ tag: 'Draw', shape })
export const randomSeed = /* ? one effect */
```
**Scala**
```scala
def sequence(ios: List[IO[Unit]]): IO[Unit] = ios.foldRight(/* ? */)((io, rest) => /* ? */)
lazy val randomSeed: IO[Long] = effect(Op.RandomSeed).map {
case OpResult.Seed(v) => /* ? */
case OpResult.Done => throw new IllegalStateException("RandomSeed must be answered with a Seed")
}
```
**F#**
```fsharp
let sequence (ios: IO list) : IO = List.foldBack (fun io rest -> (* ? *)) ios (* ? *)
```
Exercise 7.4 — nudge
`trace` of a `Pure` is easy: a result and no ops. For an `Effect(op, k)`, which answer do you give `k`? Once you have `k(answer)`, you have a smaller program; trace it (recursion!) and combine its result with `op`.
Exercise 7.4 — near-solution (try the others first)
**JavaScript**
```js
const answer = (op, seed) => /* seed for RandomSeed, undefined for Draw */
export const trace = (io, seed) => {
if (io.tag === 'Pure') return { result: io.value, ops: [] }
const rest = trace(/* ? */, seed)
return { result: rest.result, ops: [/* ? */, ...rest.ops] }
}
```
**Scala** (recursion that is not in tail position is fine here; `@tailrec` with an accumulator is a nice extra)
```scala
def trace[A](io: IO[A], seed: Long): Traced[A] = io match
case IO.Pure(a) => Traced(a, Nil)
case IO.Effect(op, k) =>
val rest = trace(k(/* ? answer for op */), seed)
Traced(/* ? */, /* ? */)
```
**F#**
```fsharp
let rec trace (seed: uint32) (io: IO<'a>) : Traced<'a> =
match io with
| Pure a -> { Result = a; Ops = [] }
| Effect (op, k) ->
let rest = trace seed (k ((* ? answer for op *)))
{ rest with Ops = (* ? *) }
```
## Check your understanding
1. In one sentence each: what is the difference between `Math.random()` and `randomSeed`? Between `ctx.fillRect(...)` and `draw(rect)`?
2. Why can't an `IO` be a plain list of `Op`s? Give a concrete program from the Snake game that a list could not express.
3. Predict `trace(flatMap(randomSeed, (s) => map(draw(r), () => s * 2)), 21)`: what are `result` and `ops`?
4. Predict: does `const p = map(randomSeed, (s) => { throw new Error('x') })` throw? Does `trace(p, 0)`? Does `flatMap(pure(1), () => { throw new Error('x') })`? Explain each answer using the structure of `flatMap`.
5. Why do the tests compare `trace(...)` results instead of comparing `IO` values directly? What is "observational equality", and why is it the right notion of "the same program" here?
6. State the associativity law in words. Which everyday refactoring (think "extract function") would be unsafe if it didn't hold?
7. You build `sequence` with a *left* fold and every m07 test passes. What breaks later, and why doesn't the loop in the interpreter save you?
8. To add sound to the game you'd add `{tag:'PlaySound', name}`. List every place in the code that has to change. Which place does *not* have to change at all, and why is that the point of the design?
## In production
- **Haskell's `IO`** is the original: `main :: IO ()` is a value that the runtime performs; every effectful function returns an `IO` description. Haskell's `do`-notation is the same sugar as the Scala track's `for` and F#'s computation expressions (section 11) ([Haskell wiki: IO inside](https://wiki.haskell.org/IO_inside)).
- **cats-effect** (Scala) gives you `IO[A]` with `pure`, `map`, `flatMap`, `IO.delay`/`IO.blocking` to suspend side-effecting code, and a runtime that interprets it on a fiber scheduler with stack safety, cancellation and concurrency ([cats-effect](https://typelevel.org/cats-effect/)). The book [*Functional Programming in Scala*](https://www.manning.com/books/functional-programming-in-scala-second-edition), chapter 13, builds an `IO` very much like ours and then makes it stack-safe.
- **ZIO** (Scala) uses `ZIO[R, E, A]`: a description of a program that needs an environment `R`, may fail with `E`, or succeeds with `A` ([ZIO](https://zio.dev/)). The `R` is a typed version of our `Platform`.
- **Free monads** are used directly to separate a program's instructions from their interpretation, e.g. to run the same business logic against a real database and an in-memory test double ([Gabriel Gonzalez, "You Could Have Invented Free Monads"](https://www.haskellforall.com/2012/06/you-could-have-invented-free-monads.html)).
- **Elm commands** (`Cmd msg`) are effect descriptions returned by `update`; the Elm runtime performs them and feeds results back as messages ([Elm guide: Commands and Subscriptions](https://guide.elm-lang.org/effects/)). In F#, **Elmish** has the same `Cmd` type ([Elmish](https://elmish.github.io/elmish/)).
## Further reading
- [Gabriel Gonzalez, "You Could Have Invented Free Monads"](https://www.haskellforall.com/2012/06/you-could-have-invented-free-monads.html) — the same derivation as this module, in Haskell; read it to see the idea in its native habitat.
- [Rúnar Bjarnason, "Stackless Scala With Free Monads"](http://blog.higher-order.com/assets/trampolines.pdf) — how to make this structure stack-safe for arbitrarily long programs.
- [Haskell wiki: Monad laws](https://wiki.haskell.org/Monad_laws) — the three laws with examples and the intuition behind each.
- [Mostly Adequate Guide to Functional Programming, chapters 8–9](https://mostly-adequate.gitbook.io/mostly-adequate-guide/) — functors, `IO` and monads, in JavaScript.
- [Scala: for-comprehensions](https://docs.scala-lang.org/tour/for-comprehensions.html) and [F#: Computation expressions](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions) — the syntax for writing `flatMap` chains in each language.
- [Referential transparency (Wikipedia)](https://en.wikipedia.org/wiki/Referential_transparency) — the property this whole module protects.
- [Continuation-passing style (Wikipedia)](https://en.wikipedia.org/wiki/Continuation-passing_style) — where the `k` comes from.