Last active
September 4, 2018 07:12
-
-
Save afsalthaj/8952ccbe46e786f687b474381dba3450 to your computer and use it in GitHub Desktop.
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
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