Skip to content

Instantly share code, notes, and snippets.

@adilakhter
Forked from filippovitale/FreeConsole
Created September 9, 2016 13:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adilakhter/a45911ffef03f28817ad90f96b3959f3 to your computer and use it in GitHub Desktop.
Save adilakhter/a45911ffef03f28817ad90f96b3959f3 to your computer and use it in GitHub Desktop.
FreeConsole – Simplest end to end example of Coyoneda and Free Monad in Scala
import scalaz.effect.IO
import scalaz.std.function._
import scalaz.{Coyoneda, Free, Monad, State, ~>}
object NonFunctor extends App {
// my simple algebra
sealed trait Console[A]
case class PrintLine(msg: String) extends Console[Unit]
case object ReadLine extends Console[String]
// Let's create a functor using Coyoneda
// - Coyoneda will turn any F[A] into a Coyoneda[F,A]
// - Coyoneda[F,A] is a functor
type ConsoleCoyo[A] = Coyoneda[Console, A]
// Then scalaz's Free has a type alias just for this:
// - A free monad over the free functor generated by `S`
// - type FreeC[S[_], A] = Free[({type f[x] = Coyoneda[S, x]})#f, A]
// So now We have a Free monad for console:
type ConsoleMonad[A] = Free.FreeC[Console, A]
// Free monad over the free functor of ConsoleCoyo. The instance is not inferrable.
// Useful for the for comprehension
implicit val MonadConsole: Monad[ConsoleMonad] = Free.freeMonad[({type λ[α] = Coyoneda[Console, α]})#λ]
// smart constructor of Console[A]
// also you'll find handy that scalaz's Free has a function to lift a F[A] directly into a monad:
// A free monad over a free functor of `S`
// - def liftFC[S[_], A](s: S[A]): FreeC[S, A] = liftFU(Coyoneda lift s)
def printLine(s: String) = Free.liftFC(PrintLine(s))
val readLine = Free.liftFC(ReadLine)
//////////////////////////////////////////////////////////////////////////////
val prog = for {
_ <- printLine("What is your name?")
name <- readLine
_ <- printLine(s"Hello: $name")
} yield ()
//////////////////////////////////////////////////////////////////////////
case class ConsoleContext(stdin: BufferedReader, stdout: PrintWriter)
// Natural transformation
type ConsoleReader[A] = ConsoleContext => A
val toState: Console ~> ConsoleReader =
new (Console ~> ConsoleReader) {
def apply[A](fa: Console[A]): ConsoleReader[A] =
// it constrains the meaning of A into something else
fa match {
case PrintLine(s) => c => { c.stdout.println(s); c.stdout.flush() }
case ReadLine => _.stdin.readLine()
}
}
// Now we have enough structure to run a program (every function is a reader monad)
def runConsole[A](program: ConsoleMonad[A], consoleContext: ConsoleContext): A =
Free.runFC[Console, ConsoleReader, A](program)(toState).apply(consoleContext)
val defaultConsoleContext = ConsoleContext(
new BufferedReader(new InputStreamReader(System.in)),
new PrintWriter(System.out))
implicit class ConsoleOps[A](ma: ConsoleMonad[A]) {
def exec(consoleContext: ConsoleContext): A = runConsole(ma, consoleContext)
def liftIO: IO[A] = IO(defaultConsoleContext).map(exec)
}
//////////////////////////////////////////////////////////////////////////////
case class PureConsoleContext(inputs: List[String], outputs: List[String])
// Natural transformation
type PureConsoleReader[A] = State[PureConsoleContext, A]
val toPureState: Console ~> PureConsoleReader =
new (Console ~> PureConsoleReader) {
def apply[A](fa: Console[A]): PureConsoleReader[A] =
// it constrains the meaning of A into something else
fa match {
case PrintLine(s) =>
State[PureConsoleContext, Unit] { st =>
// TODO Lens
(st.copy(outputs = s::st.outputs), ())
}
case ReadLine =>
State[PureConsoleContext, String] { st =>
// TODO Lens
st.inputs match {
case i :: is => st.copy(inputs = is) -> i
case Nil => ???;
}
}
}
}
// Now we have enough structure to run a program (every function is a reader monad)
def runPureConsole[A](program: ConsoleMonad[A], consoleContext: PureConsoleContext): (PureConsoleContext, A) = {
Free.runFC[Console, PureConsoleReader, A](program)(toPureState).run(consoleContext)
}
implicit class PureConsoleOps[A](ma: ConsoleMonad[A]) {
def exec(consoleContext: PureConsoleContext): (PureConsoleContext, A) = runPureConsole(ma, consoleContext)
}
///////////////////////////
// prog.exec(defaultConsoleContext)
println(prog.exec(PureConsoleContext(List("Filippo"), Nil)))
}
/*
friendly reminder, we are doing all this to avoid:
trait ConsoleContextOperations {
def println(s: String): Unit
def readLine(): String
}
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment