Created
October 3, 2016 16:19
-
-
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.
This file contains 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
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