Skip to content

Instantly share code, notes, and snippets.

@sortega
Created October 3, 2016 16:19
Show Gist options
  • Save sortega/87d44214f87a03e4dd7e9541edce9be4 to your computer and use it in GitHub Desktop.
Save sortega/87d44214f87a03e4dd7e9541edce9be4 to your computer and use it in GitHub Desktop.
I informally demoed the concept of Free monad to a colleague and he asked for the code. I've added a couple comments to make it standalone.
package experiments
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{Await, Future}
import scalaz._
import Scalaz._
import scala.concurrent.duration.Duration
import natural.TypeSafeMap
object ForFree extends App {
// An algebra is no more than a set of operations. We track the type of the result using the
// type parameter (a phantom parameter, see https://wiki.haskell.org/Phantom_type).
sealed trait CashflowAlgebra[A]
case class Deposit(amount: Int) extends CashflowAlgebra[Unit]
case class Withdraw(amount: Int) extends CashflowAlgebra[Unit]
case object CheckBalance extends CashflowAlgebra[Int]
// [[Free]] has all the "infrastructure" create a monadic type and smart constructors for our
// actions. (How many times do you remember yourself writing a tree of actions in Java?)
type Cashflow[A] = Free[CashflowAlgebra, A]
def deposit(amount: Int): Cashflow[Unit] = Free.liftF(Deposit(amount))
def withdraw(amount: Int): Cashflow[Unit] = Free.liftF(Withdraw(amount))
def balance: Cashflow[Int] = Free.liftF(CheckBalance)
// Some stupid business logic describing what to do... but not doing it
def businessLogic(description: String, threshold: Int): Cashflow[String] =
for {
initialBalance <- balance
_ <- if (initialBalance > threshold) withdraw(50) else deposit(5)
finalBalance <- balance
} yield s"Foo: $description with balance $finalBalance"
val program = businessLogic("hello world", 100)
// This "production" executor translate the algebra operations into futures (imagine invoking
// a remote API).
val productionExecutor = new (CashflowAlgebra ~> Future) {
override def apply[A](request: CashflowAlgebra[A]): Future[A] = request match {
case Deposit(amount) =>
Future {
println(s"Deposit of $amount")
}
case Withdraw(amount) =>
Future {
println(s"Withdrawal of $amount")
}
case CheckBalance => Future.successful(60)
}
}
val result = Await.result(program.foldMap(productionExecutor), Duration.Inf)
println(""" "production": """ + result)
// This executor uses a type-safe map to store canned results. It will blow up if expectations
// are not met. Note that you don't need to deal with futures.
// Swap the last two lines to make the "test" fail
val dummyExecutor = TypeSafeMap.empty[CashflowAlgebra] +
(CheckBalance -> 1000) +
// (Withdraw(50) -> ())
(Deposit(50) -> ())
println(""" "test": """ + program.foldMap(dummyExecutor))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment