Skip to content

Instantly share code, notes, and snippets.

@lamdor
Created November 28, 2016 21:22
Show Gist options
  • Save lamdor/1798d7e43abfd59602129f76b89cc4b0 to your computer and use it in GitHub Desktop.
Save lamdor/1798d7e43abfd59602129f76b89cc4b0 to your computer and use it in GitHub Desktop.
package eg.repository.interpreter
import cats.{ MonadError, MonadState }
import java.time.LocalDate
import scala.collection.mutable.{Map => MMap}
import cats.Eval
import cats.arrow.FunctionK
import cats.data.StateT
import cats.instances.either._
import eg.model._
import eg.repository.AccountRepository._
trait AccountRepoInterpreter[M[_]] {
def apply[A](action: AccountRepo[A]): M[A]
}
case class AccountRepoMutableInterpreter() extends AccountRepoInterpreter[Eval] {
val table = MMap.empty[String, Account]
val step: FunctionK[AccountRepoF, Eval] = new FunctionK[AccountRepoF, Eval] {
override def apply[A](fa: AccountRepoF[A]): Eval[A] = fa match {
case Query(no) =>
table.get(no)
.map(a => Eval.now(a))
.getOrElse { Eval.later(sys.error("Not found")) }
case Store(account) =>
Eval.now(table += ((account.no, account))).map(_ => ())
case Delete(no) =>
Eval.now(table -= no).map(_ => ())
}
}
def apply[A](action: AccountRepo[A]): Eval[A] =
action.foldMap(step)
}
object AccountRepoState {
type AccountMap = Map[String, Account]
type Err[A] = Either[String, A]
type AccountState[A] = StateT[Err, AccountMap, A]
}
object AccountRepoStateInterpreter extends AccountRepoInterpreter[AccountRepoState.AccountState] {
import AccountRepoState._
import StateT._
def interpret[A](fa: AccountRepoF[A]): AccountState[A] = fa match {
case Query(no) =>
inspectF { table => // could use getAccount from below
table.get(no) match {
case Some(a) => Right(a)
case None => Left("not found")
}
}
case Store(account) => modify(_.updated(account.no, account))
case Delete(no) => modify(_ - no)
} //.asInstanceOf[AccountState[A]] // needed when it was AccountRepoF[+A], variance made things go sideways
def apply[B](action: AccountRepo[B]): AccountState[B] =
action.foldMap(FunctionK.lift(interpret))
private[this] def getAccount(no: String)(table: AccountMap): Err[Account] = {
table.get(no) match {
case Some(a) => Right(a)
case None => Left("not found")
}
}
}
case class AccountRepoMonadClassInterpreter[M[_]](
implicit monadState: MonadState[M, AccountRepoState.AccountMap],
monadError: MonadError[M, String]
) extends AccountRepoInterpreter[M] {
def interpret[A](fa: AccountRepoF[A]): M[A] = fa match {
case Query(no) =>
monadState.flatMap(monadState.get) { table =>
table.get(no) match {
case None => monadError.raiseError[Account]("not found")
case Some(a) => monadState.pure(a)
}
}
case Store(account) => monadState.modify(_.updated(account.no, account))
case Delete(no) => monadState.modify(_ - no)
}
def apply[B](action: AccountRepo[B]): M[B] =
action.foldMap(FunctionK.lift(interpret))(monadState)
}
object TestInterpreters {
def main(args: Array[String]): Unit = {
val instructions = for {
_ <- store(Account("1234", "Checking", LocalDate.now))
// a <- query("1234")
_ <- update("1234", a => a.copy(name = "Checking 2"))
_ <- query("1234")
// _ <- delete("1234")
a2 <- query("1234")
} yield a2
val compiledViaState = AccountRepoStateInterpreter.apply(instructions)
val resultViaState = compiledViaState.run(Map.empty)
println(s"resultViaState = ${resultViaState}")
val compiledViaStateViaClasses =
AccountRepoMonadClassInterpreter.apply[AccountRepoState.AccountState].apply(instructions)
val resultViaStateViaClasses = compiledViaStateViaClasses.run(Map.empty)
println(s"resultViaStateViaClasses = ${resultViaStateViaClasses}")
val mutableInterpreter = AccountRepoMutableInterpreter()
val compiledViaMutable = mutableInterpreter.apply(instructions)
val resultViaMutable = compiledViaMutable.value
println(s"resultViaMutable = ${resultViaMutable}")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment