I am trying to learn how to use FreeMonads to implement interpreters for my services.
Suppose I have
sealed trait ServiceAction[T] extends Product with Serializable
case class ConsumeCommand(cmd: AccruePoints) extends ServiceAction[AccruePointModel]
case class CreateEvent(evt: PointsAccruedEvent) extends ServiceAction[PointsAccruedEvent]
sealed trait LogAction[T] extends Product with Serializable
case class Info(msg: String) extends LogAction[Unit]
case class Error(msg: String) extends LogAction[Unit]
and a Monad of the action
type LogActionF[A] = Free[LogAction, A]
type ServiceActionF[A] = Free[ServiceAction, A]
Next, I define my service like this:
trait PointAccrualService {
def consume(cmd: AccruePoints): ServiceActionF[AccruePointModel] = Free.liftF(ConsumeCommand(cmd))
def emit(evt: PointsAccruedEvent) : ServiceActionF[PointsAccruedEvent] = Free.liftF(CreateEvent(evt))
}
and
trait LogService {
def info(msg: String) : LogActionF[Unit] = Free.liftF(Info(msg))
def error(msg: String) : LogActionF[Unit] = Free.liftF(Error(msg))
}
with an object of each
object LogService extends LogService
object PointAccrualService extends PointAccrualService
My LogServiceInterpreter
is like this:
case class LogServiceConsoleInterpreter() extends LogServiceInterpreter {
def apply[A](action: LogActionF[A]): Task[A] = action.foldMap(handler)
protected def handler = new (LogAction ~> Task) {
override def apply[A](fa: LogAction[A]) = fa match {
case Info(m) =>
now(info(m))
case Error(m) =>
now(error(m))
}
}
def info(msg: String): Unit = {
println(s"INFO: $msg")
}
def error(msg: String): Unit = {
println(s"ERROR: $msg")
}
}
Similarly, my PointAccuralServiceInterpreter
is like this:
case class PointAccuralServiceInterpreter() {
def apply[A] (action: ServiceActionF[A]) : Task[A] = action.foldMap(handler)
protected def handler = new (ServiceAction ~> Task) {
override def apply[A](fa: ServiceAction[A]): Task[A] = fa match {
case ConsumeCommand(cmd) => {
println("Service ConsumeCommand:" + cmd)
now(cmd)
}
case CreateEvent(evt) => {
println("Service CreateEvent:" + evt)
now(evt)
}
}
}
}
My logic is straightforward, I want to log, and consume my command and then create an event, sort of like an event sourcing:
val ret = for {
_ <- logService.info("Command: " + cmd)
model <- service.consume(cmd)
_ <- logService.info("Model: " + model)
evt <- service.emit(model.toEvent("200", "Event Sent"))
_ <- logService.info("Event:" + evt)
} yield evt
This code doesn't even compile actually.
What should I do from here? I think I am supposed to use Coproduct to chain them and execute this piece of logic by feeding my interpreter.
I found something here https://groups.google.com/forum/#!topic/scalaz/sHxFsFpE86c
or it's said I can use Shapeless to do so Folding a list of different types using Shapeless in Scala
They are all too complicated. All I want is, after I define my logic, how do I execute it?
Hope I put enough details here for an answer. I really want to learn this. Thanks
I slightly modified your code to create a self-contained running example. I also added a possible answer to your question, how to execute your program, following RĂșnar Bjarnason's ideas, using Scalaz 7.2. (I did not find the
or
operator for the natural transformations in Scalaz, so I added it here.)I also added a few stubs to give your actions something to fiddle with and simplified your services to the handlers inside (since I had to create a new service for both languages combined). Furthermore I changed your
Task.now{...}
toTask{...}
to create an asynchronous Task, which is executed on the last line of code.Here is the full code: