Skip to content

Instantly share code, notes, and snippets.

@afsalthaj
Last active May 1, 2018 01:47
Show Gist options
  • Save afsalthaj/f0dae47f4bf72b224ea6fc300081c638 to your computer and use it in GitHub Desktop.
Save afsalthaj/f0dae47f4bf72b224ea6fc300081c638 to your computer and use it in GitHub Desktop.
package com.thaj.functionalprogramming.exercises.part4
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]
// 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)
}
}
}
}
// 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[Coproduct[Logging, BridgeExec, ?], Unit] =
inject[Logging, Coproduct[Logging, BridgeExec, ?], Unit](Info("printLogging"))
// Lifted second algebra to a Corproduct
private val bridgeCoproduct: Free[Coproduct[Logging, BridgeExec, ?], Unit] =
inject[BridgeExec, Coproduct[Logging, BridgeExec, ?], 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)
}
}
// App interpreter
val interpreter: Coproduct[Logging, BridgeExec, ?] ~> Scalaz.Id = runLogging.or(runBridge)
// Your free program that is easily testable.
val program: Free[Coproduct[Logging, BridgeExec, ?], 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