Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save afsalthaj/8952ccbe46e786f687b474381dba3450 to your computer and use it in GitHub Desktop.
Save afsalthaj/8952ccbe46e786f687b474381dba3450 to your computer and use it in GitHub Desktop.
import cats.data.{EitherT, Writer, WriterT}
import cats.free.Free
import cats.implicits._
import cats.{~>}
import scalaz.zio.IO
// Technique to build a computation description which should accumulate info on each execution step
// which is to be returned to the user regardless of failure or success.
// In other words, instead of `Left[Error] \/ Right[List[StepsExecuted]]]`,
// we get `(List[StepsExecuted], Left[Error])` using EitherT[WriterT[IO[...]]] while **failing early**.
// The gist is, in particular, handle Free monads and shows how nice it is
// compared to finally tagless where transformers can come all over the place of business logic.
// PS:
// There should be a cats monad instance for zio since I am using cats:
// {{{
// implicit def catsMonad[E]: cats.Monad[IO[E, ?]] = new cats.Monad[IO[E, ?]]{
// override def flatMap[A, B](fa: IO[E, A])(f: A => IO[E, B]): IO[E, B] = fa.flatMap(f)
// override def tailRecM[A, B](a: A)(f: A => IO[E, Either[A, B]]): IO[E, B] =
// f(a).flatMap {
// case Left(l) => tailRecM(l)(f)
// case Right(r) => IO.now(r)
// }
// override def pure[A](x: A): IO[E, A] = IO.point(x)
// }
//
// }}}
// Also I have used my own `asIO` to convert zio to cats for easily running without extending RTS.
// You may please choose to run with in ZIO itself.
// Apart from all these, everything else should work straight away!
// Another version of same gist is in here with Kleisli as target monad:
// https://gist.github.com/afsalthaj/b5232ca198e27c369e899c2d83b6de2e
sealed trait Alg[_]
object Alg {
type Action[A] = Free[Alg, A]
case object Action1 extends Alg[Int]
case object Action2 extends Alg[Int]
def action1: Action[Int] = Action1.asAction
def action2: Action[Int] = Action2.asAction
}
trait InterpreterSyntax extends InterpreterTypes {
implicit class ZIOSynstax[E, A](val f: IO[String, A]) {
def ~>(msg: String): ActionWithLogT[A] =
EitherT[ActionWithLog, String, A] {
WriterT[ZIO, List[String], Either[String, A]](f.attempt.map(t => (List(msg), t)))
}
}
}
trait InterpreterTypes {
// A computation that can never crash.
type ZIO[A] = IO[Nothing, A]
// We use WriterT to accumulate the result of execution steps to be passed back to the user.
// Pls note, this approach is not for sys-log. If you are looking for actual logging,
// it's better off not to delay it using WriterTs and use one of the dozens of approaches available.
type ActionWithLog[A] = WriterT[ZIO, List[String], A]
type ActionWithLogT[A] = EitherT[ActionWithLog, String, A]
}
trait Interpreter extends InterpreterSyntax {
import Alg._
val interpreter: Alg ~> ActionWithLogT = new (Alg ~> ActionWithLogT) {
override def apply[A](fa: Alg[A]): ActionWithLogT[A] = fa match {
case Action1 => IO.point(1) ~> "Getting 1"
case Action2 => IO.fail("painful error") ~> "Failed"
}
}
}
object Main extends App with Interpreter {
import Alg._
/**
* Free algebra version. Looks nicer ! It looks horrible in a finally tagless if we are composing this
* as we are early with eithert's and writert's and business logic interpreter level.
* In finally tagless, it would mostly be
* {{{
* // ||> lifts to WriterTs.
* _ <- EitherT { 4.asRight[Throwable] ||> "Getting 4" }
* _ <- EitherT { 5.asRight[Throwable] ||> "Getting 5" }
* _ <- EitherT { new Exception("Failed 1").asLeft[Int] ||> "Getting 6" }
* _ <- EitherT { new Exception("Failed 2").asLeft[Int] ||> "Getting 7" }
* }}}
*/
val comp: Free[Alg, Unit] =
for {
_ <- action1
_ <- action1
_ <- action1
_ <- action1
_ <- action1
_ <- action1
_ <- action1
_ <- action1
_ <- action1
_ <- action1
_ <- action2
_ <- action2
_ <- action2
_ <- action2
} yield ()
val res = comp.foldMap(interpreter).value.run.asIO.unsafeRunSync()
println(res)
// (List(
// Getting 1, Getting 1, Getting 1, Getting 1,
// Getting 1, Getting 1,
// Getting 1, Getting 1, Getting 1, Getting 1, Failed
// ), Left(painful error))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment