Skip to content

Instantly share code, notes, and snippets.

@erichonorez
Created December 13, 2016 20:39
Show Gist options
  • Save erichonorez/a2a2f48f142473ae17dae620f4b4027e to your computer and use it in GitHub Desktop.
Save erichonorez/a2a2f48f142473ae17dae620f4b4027e to your computer and use it in GitHub Desktop.
State Monad
import scalaz._
import Scalaz._
sealed trait Transaction {
def value: BigDecimal
def apply(amount: BigDecimal): BigDecimal
}
case class Credit(value: BigDecimal) extends Transaction {
def apply(amount: BigDecimal): BigDecimal = {
amount + value
}
}
case class Debit(value: BigDecimal) extends Transaction {
def apply(amount: BigDecimal): BigDecimal = {
amount - value
}
}
sealed trait Event
case class AccountCredited() extends Event
case class AccountDebited() extends Event
case class Account(transactions: Seq[Transaction]) {
def credit(value: BigDecimal): State[Account, Seq[Event]] = {
State { s =>
(Account(s.transactions :+ Credit(value)), Seq(AccountCredited()))
}
}
def debit(value: BigDecimal): State[Account, Seq[Event]] = {
State { s =>
if (s.balance < value) throw new IllegalArgumentException
(Account(s.transactions :+ Debit(value)), Seq(AccountDebited()))
}
}
def balance = transactions.foldLeft(BigDecimal.valueOf(0))((zero, b) => b.apply(zero) )
}
type AccountS[A] = State[Account, A]
type ET[F[_], A] = EitherT[F, Exception, A]
type AccountOpES[A] = ET[AccountS, A]
object AccountOpES {
def apply[A](s: AccountS[Exception \/ A]): AccountOpES[A] = EitherT(s)
def liftE[A](e: Exception \/ A): AccountOpES[A] = apply(Applicative[AccountS].point(e))
def liftS[A](s: AccountS[A]): AccountOpES[A] = MonadTrans[ET].liftM(s)
}
type AccountOp = State[Account, Exception \/ Seq[Event]]
object AccountService {
def credit(value: BigDecimal): AccountOpES[Seq[Event]] = AccountOpES {
State { s =>
(Account(s.transactions :+ Credit(value)), \/-(Seq(AccountCredited())))
}
}
def debit(value: BigDecimal): AccountOpES[Seq[Event]] = AccountOpES {
State { s =>
if (s.balance < value) (s, -\/(new IllegalArgumentException))
else (Account(s.transactions :+ Debit(value)), \/-(Seq(AccountDebited())))
}
}
def balance(account: Account) = account.transactions.foldLeft(BigDecimal.valueOf(0))((zero, b) => b.apply(zero) )
}
import AccountService._
println((for {
_ <- credit(0)
_ <- debit(2)
r <- credit(10)
} yield r).run(new Account(Seq())))
println(credit(0).run(new Account(Seq())))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment