Created
June 16, 2017 14:20
-
-
Save fsarradin/f8fc8c316249a09e7ffdc1c9a9937a55 to your computer and use it in GitHub Desktop.
Free and State are in a bank
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
import cats.data.State | |
import cats.free.Free | |
import cats.free.Free.liftF | |
import cats.kernel.Monoid | |
import cats.~> | |
type Amount = Double | |
// ADT for bank operations | |
sealed trait BankOperation[A] | |
case class Deposit(amount: Amount) extends BankOperation[Unit] | |
case class Withdraw(amount: Amount) extends BankOperation[Unit] | |
case object GetBalance extends BankOperation[Amount] | |
// Account value (with some basic operations) | |
case class Account(balance: Amount) { | |
def deposit(amount: Amount) = Account(balance + amount) | |
def withdraw(amount: Amount) = Account(balance - amount) | |
} | |
// Account is a monoid | |
implicit val accountMonoid: Monoid[Account] = new Monoid[Account] { | |
// when opening an account, the balance is 0 before the initial deposit | |
override def empty = Account(0.0) | |
override def combine(x: Account, y: Account) = Account(x.balance + y.balance) | |
} | |
// Convert the bank operation ADT into monad | |
// allow bank operation to be used in for-comprehension | |
type BankOperationK[A] = Free[BankOperation, A] // TODO find a better name | |
def deposit(amount: Amount): BankOperationK[Unit] = liftF(Deposit(amount)) | |
def withdraw(amount: Amount): BankOperationK[Unit] = liftF(Withdraw(amount)) | |
def getBalance: BankOperationK[Amount] = liftF(GetBalance) | |
// Capture the variations of an account | |
type AccountState[A] = State[Account, A] | |
// translate bank operations into account state modifications | |
def interpreter: BankOperation ~> AccountState = | |
new (BankOperation ~> AccountState) { | |
override def apply[A](operation: BankOperation[A]): AccountState[A] = | |
operation match { | |
case Deposit(amount) => State.modify[Account](_.deposit(amount)) | |
case Withdraw(amount) => State.modify[Account](_.withdraw(amount)) | |
case GetBalance => State.inspect[Account, Amount](_.balance) | |
} | |
} | |
def interpret[A](accountMovements: BankOperationK[A]): A = | |
accountMovements.foldMap(interpreter).runEmptyA.value | |
// -- Test -- | |
// stack up account movements | |
val accountMovements: BankOperationK[Amount] = | |
for { | |
_ <- deposit(1000) | |
_ <- withdraw(10) | |
_ <- withdraw(23) | |
_ <- deposit(300) | |
_ <- withdraw(400) | |
b <- getBalance | |
} yield b | |
interpret(accountMovements) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment