liveBook · Manning

Step

As explained in Functional Programming in Scala, Second Edition

step is a Free interpreter operation that reassociates and simplifies a computation into a normalized Return, Suspend, or right-associated FlatMap form.

`step` is an interpreter helper for `Free[F, A]` that exposes a small set of executable shapes before `run` continues. It rewrites a computation until it is a `Return`, a `Suspend`, or a right-associated `FlatMap(Suspend(fx), f)`. This normalization keeps the runner focused on immediate work rather than every possible nesting of `flatMap`. section 13.10

The difficult case is a left-nested bind. A value shaped like `FlatMap(FlatMap(fx, f), g)` is reassociated as `fx.flatMap(x => f(x).flatMap(g))`, then stepped again. A bind whose source is already `Return(x)` is simplified by applying its continuation, `f(x)`, and stepping that result. All remaining shapes are already normalized. section 13.10

@annotation.tailrec
final def step: Free[F, A] = this match
  case FlatMap(FlatMap(fx, f), g) => fx.flatMap(x => f(x).flatMap(g)).step
  case FlatMap(Return(x), f) => f(x).step
  case _ => this

The implementation makes the two rewrites explicit: reassociate nested `FlatMap` values, eliminate `FlatMap(Return(x), f)`, and leave normalized cases unchanged. section 13.10

Running a normalized Free program

After calling `step`, `run` has three productive cases: lift a returned value with `unit`, return a suspended effect directly, or sequence one suspended effect with the remaining continuation. The final wildcard is marked impossible because `step` has removed left-nested binds and binds over `Return`. section 13.10

The same operational idea appears in `Pull`: stepping incrementally either reports final result `R` or emits one output `O` with a remainder pull. Its `FlatMap` case reassociates nested binds and propagates the continuation onto the tail, allowing `fold` to repeatedly consume one step at a time. section 15.2

When pulls gain error handling, stepping also interprets nested error handlers. It right-associates nested handlers, delegates failures to `handleErrorWith`, and carries an unchanged handler forward with the remainder. With resource scopes, a step additionally reports the active scope so evaluation can open, restore, and close scopes correctly. section 15.3.2 section 15.3.3

How Functional Programming in Scala, Second Edition covers it

How deep it goes
Works through reassociation and return-bind simplification, then defers effect execution to `run`.
Examples
Uses tail-recursive `step` and `run` implementations, plus Pull outputs and remainders.
What it assumes
Assumes familiarity with Free constructors and flatMap while making normalization rules explicit.
What you can do after
Evaluate/understand it by tracing how nested binds become cases an interpreter can execute.
How it connects
Connects Free, FlatMap, Return, Suspend, Monad, Pull, and stream folding.

This explainer was generated by Manning's knowledge graph from the text of Functional Programming in Scala, Second Edition. The section links point to the parts of the book it draws on.