Console I/O models line-oriented interaction as a small algebra of effects rather than exposing unrestricted side effects. A Console[A] computation is either ReadLine, yielding Option[String], or PrintLine, yielding Unit. section 13.4.2
A console-only program
Free[Console, A] makes the allowed interaction explicit: print a prompt, read a line, and transform it into an integer with readInt. The program can be composed before anything runs. section 13.4.2
Console itself cannot implement flatMap because ReadLine has no result until interpretation. Free defers composition, while runFree translates each Console operation into a target monad such as Function0 or Par. section 13.4.2
Listing 13.6 Creating our Console type
enum Console[A]:
case ReadLine extends Console[Option[String]]
case PrintLine(line: String) extends Console[Unit]
def toPar: Par[A] = this match #1
case ReadLine => Par.lazyUnit(Try(readLine()).toOption)
case PrintLine(line) => Par.lazyUnit(println(line))
def toThunk: () => A = this match #2
case ReadLine => () => Try(readLine()).toOption
case PrintLine(line) => () => println(line)
The Console definition and its interpreters show how the same description becomes a thunk or parallel computation. section 13.4.2
