Last active
September 17, 2018 02:24
-
-
Save afsalthaj/eeff5c7dc6d467618886f58deb17697f 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.free.Free | |
import cats.{ Monad, ~>} | |
import iota._ | |
import TListK.::: | |
import cats.implicits._ | |
// A monkey patching that was possible with Free Monad, where logging is | |
// never a part of the algebra/interface/interpreter. | |
// Unlike dozens of blogs and patterns, here Log is never a part of the coproduct of algebras | |
// (which can clutter the business-logic/monadic-for-comprehension). | |
object TestLogging extends App { | |
sealed trait Alg1[A] | |
object Alg1 { | |
final case class CreateStorage(x: String) extends Alg1[String] | |
final case class DeleteStorage(x: String) extends Alg1[String] | |
def createStorage(x: String): Free[Alg1, String] = CreateStorage(x).asAction | |
def deleteStorage(x: String): Free[Alg1, String] = DeleteStorage(x).asAction | |
} | |
sealed trait Alg2[A] | |
object Alg2 { | |
final case class CreateSql(x: String) extends Alg2[String] | |
final case class DeleteSql(x: String) extends Alg2[String] | |
def createSql(x: String): Free[Alg2, String] = CreateSql(x).asAction | |
def deleteSql(x: String): Free[Alg2, String] = DeleteSql(x).asAction | |
} | |
implicit val intAlg1: Alg1 ~> cats.Id = new (Alg1 ~> cats.Id) { | |
override def apply[A](fa: Alg1[A]): cats.Id[A] = fa match { | |
case CreateStorage(x) => s"created storage${x}" | |
case DeleteStorage(x) => s"deleted storage${x}" | |
} | |
} | |
implicit val intAlg2: Alg2 ~> cats.Id = new (Alg2 ~> cats.Id) { | |
override def apply[A](fa: Alg2[A]): cats.Id[A] = fa match { | |
case CreateSql(x) => s"created sql${x}" | |
case DeleteSql(x) => s"deleted sql${x}" | |
} | |
} | |
// This is our main program. It is a coproduct of all algebras. | |
type Program[A] = CopK[Alg1 ::: Alg2 ::: TNilK, A] | |
// The program being lifted to Free. | |
type FreeProgram[A] = Free[Program, A] | |
// Given Alg1 ~> F, Alg2 ~> F, we get Program ~> F, and that will be our interpeter | |
object Program { | |
def interpreter[F[_]]( | |
implicit M: Monad[F], | |
I1: Alg1 ~> F, | |
I2: Alg2 ~> F, | |
): Program ~> F = CopK.FunctionK.summon | |
} | |
// Forget about this. It is Free monad usual boiler plate | |
def lift[A[_], B[α] <: iota.CopK[_, α]]( | |
implicit I: CopK.Inject[A, B] | |
): A ~> Free[B, ?] = | |
new (A ~> Free[B, ?]) { | |
override def apply[α](fa: A[α]): Free[B, α] = | |
Free.inject[A, B](fa) | |
} | |
// It says Free[Alg, A] can be coverted to Free[Program, A]. Usual free stuff :O | |
implicit class FreeOps[F[_], A](x: Free[F, A]) { | |
def liftF(implicit I: CopK.Inject[F, Program]): Free[Program, A] = x.foldMap(lift[F, Program]) | |
} | |
// Here goes the main stuff. | |
import Alg1._ | |
import Alg2._ | |
def program(x: String): Free[Program, String] = | |
for { | |
a <- createSql(x).liftF | |
b <- deleteSql(x).liftF | |
c <- createStorage(x).liftF | |
d <- deleteStorage(x).liftF | |
} yield a + b + c + d | |
// A program without logging, and that's it. | |
println(program("afsal").foldMap(Program.interpreter[cats.Id])) | |
// [info] created sqlafsaldeleted sqlafsalcreated storageafsaldeleted storageafsal | |
// Let's add logging. let's don't touch interpreters, or the above for comprehension. | |
// Let's have separate logging module. Given a Program ~> F (which we already have) and a Logging ~> F where | |
// Logging is another algebra, then we can get Program ~> F that includes logging! | |
import Logging._ | |
def logModule[F[_] : Monad](x: Program ~> F, log: Logging ~> F): Program ~> F = new (Program ~> F) { | |
val StorageApp= CopK.Inject[Alg1, Program] | |
val SqlApp = CopK.Inject[Alg2, Program] | |
// We can be more lazy-developer here. Just do | |
// override def apply[A](fa: Program[A]): F[A] = log(info(fa.toString)) >> x(fa), where toString is from iota. | |
override def apply[A](fa: Program[A]): F[A] = fa match { | |
case StorageApp(alg) => alg match { | |
case CreateStorage(st) => log(info(s"Trying to create a storage $st")) *> x(fa) | |
case DeleteStorage(st) => log(info(s"Trying to delete a storage $st")) *> x(fa) | |
} | |
case SqlApp(alg) => alg match { | |
case CreateSql(sq) => log(info(s"Trying to create a sql $sq")) *> x(fa) | |
// I don't want to log other operations. | |
case _ => x(fa) | |
} | |
} | |
} | |
val withLog: ~>[Program, cats.Id] = logModule[cats.Id](Program.interpreter[cats.Id], Logging.logg) | |
// Same prgogram with oogging | |
println(program("afsal").foldMap(withLog)) | |
// [info] Trying to create a sql afsal | |
// [info] Trying to create a storage afsal | |
// [info] Trying to delete a storage afsal | |
// [info] created sqlafsaldeleted sqlafsalcreated storageafsaldeleted storageafsal | |
// | |
// | |
} | |
// The Logger algebra like this: | |
import cats.{Applicative, Eval, Later, ~>} | |
sealed trait Logging[A] | |
object Logging { | |
final case class Debug(msg: Eval[String]) extends Logging[Unit] | |
final case class Info(msg: Eval[String]) extends Logging[Unit] | |
final case class Warn(msg: Eval[String]) extends Logging[Unit] | |
final case class Error(msg: Eval[String], throwable: Eval[Option[Throwable]]) extends Logging[Unit] | |
// Better use smart constructors and avoid digging into the guts of log data structure. | |
def debug(msg: => String): Logging[Unit] = Debug(Later(msg)) | |
def info(msg: => String): Logging[Unit] = Info(Later(msg)) | |
def warn(msg: => String): Logging[Unit] = Warn(Later(msg)) | |
def error(msg: => String, throwable: => Option[Throwable]): Logging[Unit] = | |
Error(Later(msg), Later(throwable)) | |
// My naive Logging | |
val logg = new (Logging ~> cats.Id){ | |
override def apply[A](fa: Logging[A]): cats.Id[A] = fa match { | |
case Debug(msg) => println(msg.value) | |
case Info(msg) => println(msg.value) | |
case Warn(msg) => println(msg.value) | |
case Error(msg, throwable) => println(msg.value) | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment