Skip to content

Instantly share code, notes, and snippets.

@fsarradin
Created June 16, 2017 14:20
Show Gist options
  • Save fsarradin/f8fc8c316249a09e7ffdc1c9a9937a55 to your computer and use it in GitHub Desktop.
Save fsarradin/f8fc8c316249a09e7ffdc1c9a9937a55 to your computer and use it in GitHub Desktop.
Free and State are in a bank
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