Created
August 1, 2018 13:55
-
-
Save afsalthaj/56f01a197ac014a585bc7804603b617d 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 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