Last active
March 14, 2019 00:02
-
-
Save mtranter/90051a1d13eea4875212daa731cdecf6 to your computer and use it in GitHub Desktop.
Death of Tagless Final - Exploration of some ideas from here: https://skillsmatter.com/skillscasts/13247-scala-matters
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
package yfp.io | |
import cats.effect.IO | |
import cats.MonadError | |
import cats.implicits._ | |
import yfp.ContravariantKleisli | |
import scala.util.Try | |
trait Persistence { | |
def fetch(id: Int): IO[String] | |
} | |
trait Console { | |
def write(msg: String): IO[Unit] | |
def read: IO[String] | |
} | |
object ProgramIO { | |
type Dependencies = Persistence with Console | |
private val ME = MonadError[ContravariantKleisli[IO, Console, ?], Throwable] | |
def run = for { | |
_ <- write("Enter an ID to fetch:") | |
id <- readInt(1, 5) | |
ent <- fetch(id) | |
res <- write(ent) | |
} yield res | |
private def readInt(attempt: Int, maxRetries: Int): ContravariantKleisli[IO, Console, Int] = for { | |
v <- read() | |
i <- ME.catchNonFatal(v.toInt) | |
.handleErrorWith { _ => | |
if (attempt < maxRetries) | |
write(retryPrompt(attempt, maxRetries)).flatMap(_ => readInt(attempt + 1, maxRetries)) | |
else | |
ME.raiseError(new Exception("Too many Bad Integer attempts")) | |
} | |
} yield i | |
private def retryPrompt(attempt: Int, maxRetries: Int) = s"Invalid Int. Try Again. You have ${maxRetries - attempt} more attempts" | |
private def read(): ContravariantKleisli[IO, Console, String] = ContravariantKleisli(_.read) | |
private def write(msg: String): ContravariantKleisli[IO, Console, Unit] = ContravariantKleisli(_.write(msg)) | |
private def fetch(id: Int): ContravariantKleisli[IO, Persistence, String] = ContravariantKleisli(_.fetch(id)) | |
} | |
object ApplicationIO { | |
trait EchoPersistence extends Persistence { | |
def fetch(id: Int): IO[String] = IO.pure(id.toString) | |
} | |
trait RealConsole extends Console { | |
def write(msg: String): IO[Unit] = IO.delay(println(msg)) | |
def read: IO[String] = IO.delay(scala.io.StdIn.readLine()) | |
} | |
object IODepends extends EchoPersistence with RealConsole | |
def main(args: Array[String]) = { | |
ProgramIO.run(IODepends).unsafeRunSync() | |
} | |
} |
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
package yfp.polyf | |
import cats.MonadError | |
import cats.data.Kleisli | |
import cats.effect.IO | |
import cats.implicits._ | |
import yfp.ContravariantKleisli | |
import scala.util.Try | |
trait Persistence[F[_]] { | |
def fetch(id: Int): F[String] | |
} | |
trait Console[F[_]] { | |
def write(msg: String): F[Unit] | |
def read: F[String] | |
} | |
object Program { | |
def apply[F[_]: MonadError[?[_], Throwable]]: Program[F] = new Program[F] | |
} | |
class Program[F[_]: MonadError[?[_], Throwable]] { | |
type Dependencies = Persistence[F] with Console[F] | |
private val ME = MonadError[ContravariantKleisli[F, Console[F], ?], Throwable] | |
def run = for { | |
_ <- write("Enter an ID to fetch:") | |
id <- readInt(1, 5) | |
ent <- fetch(id) | |
res <- write(ent) | |
} yield res | |
private def readInt(attempt: Int, maxRetries: Int): ContravariantKleisli[F, Console[F], Int] = for { | |
v <- read() | |
i <- ME.catchNonFatal(v.toInt) | |
.handleErrorWith { _ => | |
if (attempt < maxRetries) | |
write(retryPrompt(attempt, maxRetries)).flatMap(_ => readInt(attempt + 1, maxRetries)) | |
else | |
ME.raiseError(new Exception("Too many Bad Integer attempts")) | |
} | |
} yield i | |
private def retryPrompt(attempt: Int, maxRetries: Int) = s"Invalid Int. Try Again. You have ${maxRetries - attempt} more attempts" | |
private def read(): ContravariantKleisli[F, Console[F], String] = ContravariantKleisli(_.read) | |
private def write(msg: String): ContravariantKleisli[F, Console[F], Unit] = ContravariantKleisli(_.write(msg)) | |
private def fetch(id: Int): ContravariantKleisli[F, Persistence[F], String] = ContravariantKleisli(_.fetch(id)) | |
} | |
object Application { | |
trait EchoPersistence extends Persistence[IO] { | |
def fetch(id: Int): IO[String] = IO.pure(id.toString) | |
} | |
trait RealConsole extends Console[IO] { | |
def write(msg: String): IO[Unit] = IO.delay(println(msg)) | |
def read: IO[String] = IO | |
.delay(scala.io.StdIn.readLine()) | |
} | |
object IODepends extends EchoPersistence with RealConsole | |
def main(args: Array[String]) = { | |
Program[IO].run(IODepends).attempt.unsafeRunSync().left.foreach(t => println(t.getMessage)) | |
} | |
} |
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
package yfp | |
import cats.{MonadError, FlatMap, Functor } | |
case class ContravariantKleisli[F[_], -A, B](run: A => F[B]) { | |
def apply[AA <: A](a: AA): F[B] = run(a) | |
def map[AA <: A, C](f: B => C)(implicit F: Functor[F]): ContravariantKleisli[F, AA, C] = | |
ContravariantKleisli(a => F.map(run(a))(f)) | |
def flatMap[AA <: A, C](f: B => ContravariantKleisli[F, AA, C])(implicit F: FlatMap[F]): ContravariantKleisli[F, AA, C] = | |
ContravariantKleisli(a => F.flatMap[B, C](run(a))((b: B) => f(b).run(a))) | |
} | |
object ContravariantKleisli extends ContravariantKleisliInstances | |
trait ContravariantKleisliInstances { | |
implicit def applicativeError[F[_], AA, E](implicit ME: MonadError[F, E]) = | |
new MonadError[ContravariantKleisli[F,AA,?], E] { | |
override def raiseError[A](e: E): ContravariantKleisli[F, AA, A] = ContravariantKleisli(_ => ME.raiseError(e)) | |
override def handleErrorWith[A](fa: ContravariantKleisli[F, AA, A])(f: E => ContravariantKleisli[F, AA, A]): ContravariantKleisli[F, AA, A] = ContravariantKleisli { a: AA => | |
ME.handleErrorWith(fa.run(a))((e: E) => f(e).run(a)) | |
} | |
override def pure[A](x: A): ContravariantKleisli[F, AA, A] = ContravariantKleisli(_ => ME.pure(x)) | |
override def ap[A, B](ff: ContravariantKleisli[F, AA, A => B])(fa: ContravariantKleisli[F, AA, A]): ContravariantKleisli[F, AA, B] = ff.flatMap(fa.map) | |
override def flatMap[A, B](fa: ContravariantKleisli[F, AA, A])(f: A => ContravariantKleisli[F, AA, B]): ContravariantKleisli[F, AA, B] = fa.flatMap(a => f(a)) | |
override def tailRecM[A, B](a: A)(f: A => ContravariantKleisli[F, AA, Either[A, B]]): ContravariantKleisli[F, AA, B] = ContravariantKleisli[F, AA, B]({ b => | |
ME.tailRecM(a) { f(_).run(b) } | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment