Hello there,
I'm trying to implement the solution for Exercise 5.1. I managed to do it for the mutable state interpreter but I cannot seem to get it to work for the State interpreter even though I've aligned the types
The following mutable state interpreter works perfectly
// works fine
class AccountRepoMutableInterpreter extends AccountRepoInterpreter[Task] {
// mutable state
var table = Map.empty[String, Account]
// Natural transformation from Free Monad to Task is the interpreter
val interpreter = new (AccountRepoInstruction ~> Task) {
override def apply[A](fa: AccountRepoInstruction[A]): Task[A] = fa match {
// give meaning to each instruction
case Query(no) =>
Task.now(table.get(no).get)
case Store(acc) =>
Task.now(table = table + (acc.no -> acc)).void
case Delete(no) =>
Task.now(table = table - no).void
}
}
override def apply[A](instruction: AccountRepo[A]): Task[A] =
instruction.foldMap(interpreter)
}
import AccountService._
val testInterpreter = new AccountRepoMutableInterpreter
val account = Account("a-123", "John K")
val comp = for {
a <- store(account.copy(balance = Balance(1000)))
q <- query(account.no)
c <- delete(account.no)
_ <- store(account.copy(balance = Balance(2000)))
} yield ()
// interpret free instructions and give it meaning getting back a Task and execute it synchronously
testInterpreter(comp).unsafePerformSync
println(testInterpreter.table)
}
However, the state implementation fails to compile even though the types align correctly (according to IntelliJ
// does not work even though types align
class AccountRepoStateInterpreter extends AccountRepoInterpreter[AccountState] {
val interpreter = new (AccountRepoInstruction ~> AccountState) {
override def apply[A](fa: AccountRepoInstruction[A]): AccountState[A] = fa match {
case Query(no) =>
val errMsg: Err[(AccountMap, Account)] = -\/(new Error("could not find member"))
val result: AccountState[Account] = StateT[Err, AccountMap, Account] {
accountMap =>
accountMap.get(no) match {
case Some(acc) =>
\/-( (accountMap, acc) )
case None =>
errMsg
}
}
result
case Store(acc) =>
val result: AccountState[Unit] = StateT[Err, AccountMap, Unit] { accountMap =>
val updatedMap = accountMap + (acc.no -> acc)
\/-( (updatedMap, ()) )
}
result
case Delete(no) =>
val result: AccountState[Unit] = StateT[Err, AccountMap, Unit] { accountMap =>
val updatedMap = accountMap - no
\/-( (updatedMap, ()) )
}
result
}
}
override def apply[A](instruction: AccountRepo[A]): AccountState[A] = instruction.foldMap(interpreter)
}
Any idea what I'm missing here?
Thanks a lot
Here is the full example
import java.util.Date
import scala.language.higherKinds
import scalaz.concurrent.Task
object Example extends App {
import scalaz._
import Scalaz._
// https://github.com/debasishg/frdomain/tree/master/src/main/scala/frdomain/ch5/domain/model
import frdomain.ch5.domain.model._
// https://github.com/debasishg/frdomain/blob/master/src/main/scala/frdomain/ch5/domain/model/Account.scala#L10
import common._
case class Balance(amount: Amount = 0)
case class Account(no: String, name: String, dateOfOpening: Date = today, dateOfClosing: Option[Date] = None,
balance: Balance = Balance(0))
object Account {
implicit val showAccount: Show[Account] = Show.shows { case a: Account => a.toString }
}
sealed trait AccountRepoInstruction[+A]
case class Query(no: String) extends AccountRepoInstruction[Account]
case class Store(account: Account) extends AccountRepoInstruction[Unit]
case class Delete(no: String) extends AccountRepoInstruction[Unit]
type AccountRepo[A] = Free[AccountRepoInstruction, A]
trait AccountRepository {
def store(account: Account): AccountRepo[Unit] =
Free.liftF(Store(account))
def query(no: String): AccountRepo[Account] =
Free.liftF(Query(no))
def delete(no: String): AccountRepo[Unit] =
Free.liftF(Delete(no))
def update(no: String, update: Account => Account): AccountRepo[Unit] =
for {
acc <- query(no)
_ <- store(update(acc))
} yield ()
def updateBalance(no: String, amount: Amount, update: (Account, Amount) => Account): AccountRepo[Unit] =
for {
acc <- query(no)
_ <- store(update(acc, amount))
} yield ()
}
trait AccountService[Account, Amount, Balance] {
def open(no: String, name: String, openingDate: Option[Date]): AccountRepo[Account]
def close(no: String, closeDate: Option[Date]): AccountRepo[Account]
def debit(no: String, amount: Amount): AccountRepo[Account]
def credit(no: String, amount: Amount): AccountRepo[Account]
def balance(no: String): AccountRepo[Balance]
}
object AccountService extends AccountService[Account, Amount, Balance] with AccountRepository {
override def open(no: String, name: String, openingDate: Option[Date]) = {
for {
_ <- store(Account(no, name, openingDate.getOrElse(today)))
acc <- query(no)
} yield acc
}
override def close(no: String, closeDate: Option[Date]): AccountRepo[Account] = for {
_ <- update(no, acc => acc.copy(dateOfClosing = Some(today)))
a <- query(no)
} yield a
override def debit(no: String, amount: Amount): AccountRepo[Account] = for {
_ <- updateBalance(no, amount, (acc, am) => acc.copy(balance = Balance(acc.balance.amount - am)))
a <- query(no)
} yield a
override def credit(no: String, amount: Amount): AccountRepo[Account] = for {
_ <- updateBalance(no, amount, (acc, am) => acc.copy(balance = Balance(acc.balance.amount + am)))
a <- query(no)
} yield a
override def balance(no: String): AccountRepo[Balance] =
query(no).map(acc => acc.balance)
}
trait AccountRepoInterpreter[M[_]] {
def apply[A](instruction: AccountRepo[A]): M[A]
}
type AccountMap = Map[String, Account]
type Err[A] = Error \/ A
type AccountState[A] = StateT[Err, AccountMap, A]
// does not work even though types align
class AccountRepoStateInterpreter extends AccountRepoInterpreter[AccountState] {
val interpreter = new (AccountRepoInstruction ~> AccountState) {
override def apply[A](fa: AccountRepoInstruction[A]): AccountState[A] = fa match {
case Query(no) =>
val errMsg: Err[(AccountMap, Account)] = -\/(new Error("could not find member"))
val result: AccountState[Account] = StateT[Err, AccountMap, Account] {
accountMap =>
accountMap.get(no) match {
case Some(acc) =>
\/-( (accountMap, acc) )
case None =>
errMsg
}
}
result
case Store(acc) =>
val result: AccountState[Unit] = StateT[Err, AccountMap, Unit] { accountMap =>
val updatedMap = accountMap + (acc.no -> acc)
\/-( (updatedMap, ()) )
}
result
case Delete(no) =>
val result: AccountState[Unit] = StateT[Err, AccountMap, Unit] { accountMap =>
val updatedMap = accountMap - no
\/-( (updatedMap, ()) )
}
result
}
}
override def apply[A](instruction: AccountRepo[A]): AccountState[A] = instruction.foldMap(interpreter)
}
val stateInterpreter = new AccountRepoStateInterpreter
// works fine
class AccountRepoMutableInterpreter extends AccountRepoInterpreter[Task] {
// mutable state
var table = Map.empty[String, Account]
// Natural transformation from Free Monad to Task is the interpreter
val interpreter = new (AccountRepoInstruction ~> Task) {
override def apply[A](fa: AccountRepoInstruction[A]): Task[A] = fa match {
// give meaning to each instruction
case Query(no) =>
Task.now(table.get(no).get)
case Store(acc) =>
Task.now(table = table + (acc.no -> acc)).void
case Delete(no) =>
Task.now(table = table - no).void
}
}
override def apply[A](instruction: AccountRepo[A]): Task[A] =
instruction.foldMap(interpreter)
}
import AccountService._
val testInterpreter = new AccountRepoMutableInterpreter
val account = Account("a-123", "John K")
val comp = for {
a <- store(account.copy(balance = Balance(1000)))
q <- query(account.no)
c <- delete(account.no)
_ <- store(account.copy(balance = Balance(2000)))
} yield ()
// interpret free instructions and give it meaning getting back a Task and execute it synchronously
testInterpreter(comp).unsafePerformSync
println(testInterpreter.table)
}
I'm using Scala 7.2.8 and Scala 2.12.1