Skip to content

Instantly share code, notes, and snippets.

@afsalthaj
Created August 1, 2018 13:55
Show Gist options
  • Save afsalthaj/56f01a197ac014a585bc7804603b617d to your computer and use it in GitHub Desktop.
Save afsalthaj/56f01a197ac014a585bc7804603b617d to your computer and use it in GitHub Desktop.
import scalaz.{Scalaz, _}
import Scalaz._
// Thanks to https://underscore.io/blog/posts/2017/03/29/free-inject.html
object CombineFreeInScalaz {
sealed trait Logging[A]
case class Info(s: String) extends Logging[Unit]
sealed trait BridgeExec[A]
case class PrintBridge(s: String) extends BridgeExec[Unit]
sealed trait BridgeExe1c[A]
case class PrintBridge1(s: String) extends BridgeExe1c[Unit]
// This is just for describing how things work, and doesn't need to defined in scalaz.
trait Inject[F[_], G[_]] {
def inj[A](fa: F[A]): G[A]
}
// Inject implicits exists in scalaz, pointed out here for better understandability.
implicit def injectCoproductLeft[F[_], X[_]]: Inject[F, Coproduct[F, X, ?]] =
new Inject[F, Coproduct[F, X, ?]] {
def inj[A](fa: F[A]): Coproduct[F, X, A] = Coproduct.leftc(fa)
}
// Inject implicits exists in scalaz, pointed out here for better understandability.
// It is similar to the above implicit definition for left, however, we assume that the right can be `R`
// specifiying it could be another coproduct or in fact it could be anything, and we use recursive implicit
// resolution.
implicit def injectCoproductRight[F[_], R[_], X[_]](implicit I: Inject[F, R]): Inject[F, Coproduct[X, R, ?]] =
new Inject[F, Coproduct[X, R, ?]] {
def inj[A](fa: F[A]): Coproduct[X, R, A] = Coproduct.rightc(I.inj(fa))
}
// Sadly, the above definition doesn't take care of the fact that what if R =:= F
implicit def injectReflexive[F[_]]: Inject[F, F] =
new Inject[F, F] {
def inj[A](fa: F[A]): F[A] = fa
}
// A helper lift function that can lift any of your algebra to a Coproduct that defines your entire app.
def inject[F[_], C[_], A](fa: F[A])(implicit m: Inject[F, C]) = Free.liftF[C, A](m.inj(fa))
// However, we have individual interpreters for each component in the app.
// But what we need is an interpreter that interprets the coproduct to some unified effect.
// Copying cats or in fact turned out to be cats implementation
def mixInterpreters[F[_], G[_], H[_]](f: F ~> H, g: G ~> H): Coproduct[F, G, ?] ~> H = {
new (Coproduct[F, G, ?] ~> H) {
def apply[A](fa: Coproduct[F, G, A]): H[A] = {
fa.run match {
case -\/(ff) => f(ff)
case \/-(gg) => g(gg)
}
}
}
}
type SubApp[A] = Coproduct[BridgeExec, BridgeExe1c, A]
type App[A] = Coproduct[Logging, SubApp, A]
// A helper to access mixInterpreters
implicit class MixInterpreterOps[F[_], H[_]](val f: F ~> H) {
def or[G[_]](g: G ~> H): Coproduct[F, G, ?] ~> H = mixInterpreters[F, G, H](f, g)
}
// Lifted first algebra to a Corproduct
private val logAsCoproduct: Free[ App, Unit] =
inject[Logging, App, Unit](Info("printLogging"))
// Lifted second algebra to a Corproduct
private val bridgeCoproduct: Free[App, Unit] =
inject[BridgeExec, App, Unit](PrintBridge("pringBridge"))
// Interpreter 1
private def runLogging: Logging ~> Id = new (Logging ~> Id) {
def apply[A](fa: Logging[A]): Id[A] = fa match {
case Info(x) => println(x)
}
}
// Interpreter 2
private def runBridge: BridgeExec ~> Id = new (BridgeExec ~> Id) {
def apply[A](fa: BridgeExec[A]): Id[A] = fa match {
case PrintBridge(x) => println(x)
}
}
private def runBridges: BridgeExe1c ~> Id = new (BridgeExe1c ~> Id) {
def apply[A](fa: BridgeExe1c[A]): Id[A] = fa match {
case PrintBridge1(x) => println(x)
}
}
val sub: ~>[Coproduct[BridgeExec, BridgeExe1c, ?], Scalaz.Id] = runBridge.or(runBridges)
// App interpreter
val interpreter: App ~> Scalaz.Id = runLogging.or[Coproduct[BridgeExec, BridgeExe1c, ?]](sub)
// Your free program that is easily testable.
val program: Free[App, Unit] =
for {
_ <- logAsCoproduct
_ <- bridgeCoproduct
} yield ()
// Call this !
def runThis = program.foldMap(interpreter)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment