Console is an algebraic data type for primitive console operations. It has two cases: ReadLine, producing Console[Option[String]], and PrintLine(line), producing Console[Unit]. 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 definition keeps console requests explicit while supplying interpreters for parallel computation and delayed execution. section 13.4.2
Embedding Console actions in Free yields programs restricted to console I/O. Free sequences requests without executing them; interpretation translates each request into a target monad such as Par or Function0. Console itself cannot implement flatMap because a ReadLine result does not exist until interpretation. section 13.4.2
A console-only program
val f1: Free[Console, Option[String]] = for _ <- printLn("I can only interact with the console.") ln <- readLn yield ln The program describes output followed by input, while its interpreter decides whether those requests perform real I/O. section 13.4.3
Interpreters can replace real console effects with Reader or State simulations: tests may ignore output, provide predetermined input, or track input and output buffers. The same restricted program can therefore support testing and execution. section 13.4.3
Console also names a capability interface whose operations return values in a chosen effect type. This reduces suspension boilerplate while preserving least-powerful programs that cannot access unrelated effects. section 13.6
