インタープリタをトランポリン化できていないが、だいたいやりたいことができた。
Free[Command, Unit]ですが、Unit以外を選択する場合ってどんなユースケースなんだろうか?
case class Money(amount: Long, currency: Currency) {
def +(other: Money): Money = {
require(currency == other.currency)
copy(amount = this.amount + other.amount)
}
def -(other: Money): Money = {
require(currency == other.currency)
copy(amount = this.amount - other.amount)
}
}
object Money {
val JPY = Currency.getInstance("JPY")
}
object AccountType extends Enumeration {
val Current, Ordinary = Value
}
case class Account(accountNumber: String,
accountType: AccountType.Value,
balance: Money = Money(0, Money.JPY)) {
private[test] def deposit(addend: Money) = {
copy(balance = this.balance + addend)
}
private[test] def withdraw(subtrahend: Money) = {
copy(balance = this.balance - subtrahend)
}
override def equals(obj: scala.Any): Boolean = obj match {
case that: Account => accountNumber == that.accountNumber
case _ => false
}
override def hashCode(): Int = accountNumber.## * 31
}
object AccountDSL {
sealed trait Command[+A]
final case class Deposit[A](addend: Money, next: A) extends Command[A]
final case class Withdraw[A](subtrahend: Money, next: A) extends Command[A]
case object Done extends Command[Nothing]
def deposit(addend: Money): Free[Command, Unit] =
Free.liftF(Deposit(addend, Done))
def withdraw(subtrahend: Money): Free[Command, Unit] =
Free.liftF(Withdraw(subtrahend, Done))
def done: Free[Command, Unit] =
Free.point(Done)
implicit val functor = new Functor[Command] {
override def map[A, B](fa: Command[A])(f: (A) => B): Command[B] = fa match {
case Deposit(addend, next) => Deposit(addend, f(next))
case Withdraw(subtrahend, next) => Withdraw(subtrahend, f(next))
case Done => Done
}
}
}
object AccountDSLRunner {
// TODO: トランポリン再帰化
private def interpret(acc: Reader[Account, Account], p: Free[Command, Unit]): Reader[Account, Account] = {
p.resume.fold({
case Deposit(v, next) =>
interpret(acc.map(_.deposit(v)), next)
case Withdraw(v, next) =>
interpret(acc.map(_.withdraw(v)), next)
}, { _ => acc })
}
def run(program: Free[Command, Unit])(account: Account) = interpret(Reader(identity), program).run(account)
}
object AccountDSLMain extends App {
lazy val program = for {
_ <- AccountDSL.deposit(Money(10, Money.JPY))
_ <- AccountDSL.withdraw(Money(5, Money.JPY))
_ <- AccountDSL.deposit(Money(10, Money.JPY))
_ <- AccountDSL.withdraw(Money(5, Money.JPY))
} yield () // () って、、、どうにかならないのか…。
val result = AccountDSLRunner.run(program)(Account("12345", AccountType.Ordinary))
println(result)
}