Skip to content

Instantly share code, notes, and snippets.

@arosien
Last active September 10, 2016 07:17
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save arosien/7241515 to your computer and use it in GitHub Desktop.
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.
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