Last active
September 10, 2016 07:17
-
-
Save arosien/7241515 to your computer and use it in GitHub Desktop.
Translation of Runar's ScalaIO 2013 presentation on IO and Free monads (http://blog.higher-order.com/assets/scalaio.pdf) to scalaz.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scalaz._ | |
import Scalaz._ | |
import Free._ | |
/** "Pure" interactions with a console. */ | |
sealed trait Console[+A] | |
case class GetLine[A](k: String => A) extends Console[A] | |
case class PutLine[A](s: String, a: A) extends Console[A] | |
object Console { | |
implicit object ConsoleFunctor extends Functor[Console] { | |
def map[A, B](fa: Console[A])(f: A => B): Console[B] = | |
fa match { | |
case GetLine(k) => GetLine(s => f(k(s))) | |
case PutLine(s, a) => PutLine(s, f(a)) | |
} | |
} | |
} | |
trait FreeHelpers { | |
// stolen from http://eed3si9n.com/learning-scalaz/Combined+Pages.html#Free+Monad | |
def liftF[F[+_]: Functor, R](command: F[R]): Free[F, R] = | |
Free.Suspend(Functor[F].map(command) { Free.Return(_) }) | |
/** Extra operations on a Free, if its functor is also a monad. */ | |
implicit class FreeMOps[M[+_]: Monad, A](free: Free[M, A]) { | |
def runI: M[A] = free.runM[M, A](identity) | |
def runF[N[+_] : Monad](f: M ~> N): N[A] = | |
free.mapSuspension(f).runI | |
} | |
} | |
/** Lift a [[Console]] into the [[scalaz.Free]] monad in order to build interpreters. */ | |
trait ConsoleIOModule extends FreeHelpers { | |
type ConsoleIO[A] = Free[Console, A] | |
def getLine[A]: ConsoleIO[String] = liftF(GetLine(identity)) | |
def putLine(s: String): ConsoleIO[Unit] = liftF(PutLine(s, ())) | |
} | |
/** Interpreters of [[ConsoleIOModule#ConsoleIO]] programs using scalaz's natural transformations. */ | |
trait ConsoleInterpreters extends ConsoleIOModule { | |
/** Interact with the real console, producing and consuming real values. */ | |
object ConsoleEffect extends (Console ~> Id) { | |
def apply[A](c: Console[A]): Id[A] = | |
c match { | |
case GetLine(k) => k(readLine) | |
case PutLine(s, a) => println(s); a | |
} | |
} | |
/** Interact with pre-defined input and output buffers. */ | |
object PureConsole extends (Console ~> Buffers) { | |
def apply[A](c: Console[A]): Buffers[A] = | |
for { | |
s <- get[InOut] | |
_ <- put(c match { | |
case GetLine(k) => InOut(s.in.tail, s.out) | |
case PutLine(l, a) => InOut(s.in, l :: s.out) | |
}) | |
} yield c match { | |
case GetLine(k) => k(s.in.head) | |
case PutLine(l, a) => a | |
} | |
} | |
/** Input/output buffers for recording interactions with a console. */ | |
case class InOut(in: List[String], out: List[String]) | |
type Buffers[+A] = State[InOut, A] | |
} | |
object ConsoleIOExamples extends ConsoleInterpreters { | |
val ask: ConsoleIO[Unit] = | |
for { | |
_ <- putLine("What is your name?") | |
name <- getLine | |
_ <- putLine("Hello, " ++ name) | |
} yield () | |
def run = | |
ask.mapSuspension(ConsoleEffect) | |
.runI | |
/* Hmm, wrong printed order: | |
> asdf | |
< Hello, asdf | |
< What is your name? | |
*/ | |
def runS = | |
ask.mapSuspension(PureConsole) | |
.runI | |
.exec(InOut(List("Alice"), Nil)) | |
// InOut(List(), List(Hello, Alice, What is your name?)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment