Last active
October 25, 2018 18:46
-
-
Save joshuakfarrar/716f80c2fa3f0090cf132379039f502d to your computer and use it in GitHub Desktop.
from the introduction of functional programming for mortals with scalaz
This file contains hidden or 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
| package LetThereBeEcho | |
| trait Terminal[C[_]] { | |
| def read: C[String] | |
| def write(t: String): C[Unit] | |
| } | |
| trait Execution[C[_]] { | |
| def doAndThen[A, B](c: C[A])(f: A => C[B]): C[B] | |
| def create[B](b: B): C[B] | |
| } | |
| object Execution { | |
| implicit class Ops[A, C[_]](c: C[A]) { | |
| def flatMap[B](f: A => C[B])(implicit e: Execution[C]): C[B] = | |
| e.doAndThen(c)(f) | |
| def map[B](f: A => B)(implicit e: Execution[C]): C[B] = | |
| e.doAndThen(c)(f andThen e.create) | |
| } | |
| implicit val deferred: Execution[IO] = new Execution[IO] { | |
| def doAndThen[A, B](c: IO[A])(f: A => IO[B]): IO[B] = c.flatMap(f) | |
| def create[B](b: B): IO[B] = IO(b) | |
| } | |
| } | |
| // a more safe execution context... IO! | |
| final class IO[A](val interpret: () => A) { | |
| def map[B](f: A => B): IO[B] = IO(f(interpret())) | |
| def flatMap[B](f: A => IO[B]): IO[B] = IO(f(interpret()).interpret()) | |
| } | |
| object IO { | |
| def apply[A](a: =>A): IO[A] = new IO(() => a) | |
| } | |
| object TerminalIO extends Terminal[IO] { | |
| def read: IO[String] = IO { io.StdIn.readLine } | |
| def write(t: String): IO[Unit] = IO { println(t) } | |
| } | |
| object Main extends App { | |
| import Execution.Ops | |
| // gross! | |
| //def echo[C[_]](t: Terminal[C], e: Execution[C]): C[String] = | |
| // e.chain(t.read) { in: String => | |
| // e.chain(t.write(in)) { _: Unit => | |
| // e.create(in) | |
| // } | |
| // } | |
| // neat! | |
| def echo[C[_]](implicit t: Terminal[C], e: Execution[C]): C[String] = | |
| for { | |
| in <- t.read | |
| _ <- t.write(in) | |
| } yield in | |
| implicit val io: Terminal[IO] = TerminalIO | |
| // because echo[C[_]] expects Terminal[C] and Execution[C] to be | |
| // implicitly available, above you can see that we pull in Terminal[IO] | |
| // with TerminalIO | |
| // can you understand why Execution[IO] exists if not reified here as an implicit? | |
| val program: IO[String] = echo[IO] | |
| program.interpret() // ...and i feel fine... | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In the case where we cannot abstract over execution context, it may be the case that the thread pool which is assigned our work does not have the features available to execute a piece of work; in this case, across environments, parameterizing over execution context buys us the ability to ensure computations being executed are computed in an environment which has available to it the features implemented necessary for the execution of that computation, though the implementation of those methods may be different, despite the similarity of their APIs.