Skip to content

Instantly share code, notes, and snippets.

@mtranter
Last active March 14, 2019 00:02
Show Gist options
  • Save mtranter/90051a1d13eea4875212daa731cdecf6 to your computer and use it in GitHub Desktop.
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
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()
}
}
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))
}
}
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