Created
December 13, 2016 20:39
-
-
Save erichonorez/a2a2f48f142473ae17dae620f4b4027e to your computer and use it in GitHub Desktop.
State Monad
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 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