runFree interprets a Free[F, A] program in a target monad G by translating every instruction F[x] into G[x]. It separates a program’s description from the interpreter’s effects: the same console program can run through real I/O, a reader, state, or a test interpreter. section 13.4.2 section 13.4.3
Interpretation proceeds through three cases after step normalizes the Free program:
- Return(a) becomes G.unit(a). section 13.4.2
- Suspend(r) becomes t(r), where t is polymorphic and converts F[x] to G[x] for every encountered x. section 13.4.2
- FlatMap(Suspend(r), f) translates r, then continues with f through the target monad’s flatMap. section 13.4.2
- step reassociates nested FlatMaps and reduces FlatMap(Return(x), f), leaving only shapes that runFree can handle. section 13.10
Interpreting console programs
A Free[Console, A] program can be interpreted with c.toThunk to produce Function0[A], with c.toPar to produce Par[A], or with c.toState to produce a stateful test interpreter. unsafeRunConsole selects the thunk interpreter and then runs it with runTrampoline. section 13.4.2 section 13.4.3
enum Free[F[_], A]:
...
def runFree[G[_]](t: [x] => F[x] => G[x])(using G: Monad[G]): G[A] = #1
step match
case Return(a) => G.unit(a)
case Suspend(r) => t(r)
case FlatMap(Suspend(r), f) => t(r).flatMap(a => f(a).runFree(t))
case FlatMap(_, _) =>
sys.error("Impossible, `step` eliminates these cases")
Inspect the compact runFree definition, including its polymorphic translation function and three interpretation cases. section 13.4.2
