The FlatMap constructor represents continuing an IO computation with the result of an earlier computation. It stores a subcomputation and a continuation function, rather than executing either immediately. section 13.3.1
The flatMap method constructs FlatMap(this, f) and returns at once. Later, the interpreter evaluates the subcomputation, passes its result to the continuation, and continues interpreting the returned IO value. Pattern matching on nested constructors preserves tail recursion instead of nesting unsafeRun calls. section 13.3.1
Repeated output
A printLine action can be repeated with printLine("Still going...").forever. This produces a nested FlatMap structure whose continuation schedules the next action; the interpreter traverses that structure and performs each effect. section 13.3.1
Listing 13.4 Creating a new IO type
enum IO[A]:
case Return(a: A) #1
case Suspend(resume: () => A) #2
case FlatMap[A, B](
sub: IO[A], k: A => IO[B]) extends IO[B] #3
def flatMap[B](f: A => IO[B]): IO[B] =
FlatMap(this, f)
def map[B](f: A => B): IO[B] =
flatMap(a => Return(f(a)))
Inspect the IO definition: FlatMap stores sub: IO[A] and k: A => IO[B], while flatMap creates the constructor directly. section 13.3.1
