我如何连接动作并与Scalaz一起解释它们?

时间:2016-12-29 19:43:07

标签: scala scalaz free-monad

我正在尝试学习如何使用FreeMonads为我的服务实现解释器。

假设我有

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]

和行动的Monad

type LogActionF[A] = Free[LogAction, A]
type ServiceActionF[A] = Free[ServiceAction, A]

接下来,我像这样定义我的服务:

trait PointAccrualService {
    def consume(cmd: AccruePoints): ServiceActionF[AccruePointModel] = Free.liftF(ConsumeCommand(cmd))
    def emit(evt: PointsAccruedEvent) : ServiceActionF[PointsAccruedEvent] = Free.liftF(CreateEvent(evt))
}

trait LogService {
  def info(msg: String) : LogActionF[Unit] = Free.liftF(Info(msg))
  def error(msg: String) : LogActionF[Unit] = Free.liftF(Error(msg))
}

每个

的对象
object LogService extends LogService
object PointAccrualService extends PointAccrualService

我的LogServiceInterpreter是这样的:

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")
  }
}

同样,我的PointAccuralServiceInterpreter是这样的:

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)
      }
    }
  }
}

我的逻辑很简单,我想记录,然后使用我的命令然后创建一个事件,有点像事件采购:

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

这段代码甚至没有实际编译。

我该怎么办?我想我应该使用Coproduct链接它们并通过提供我的解释器来执行这条逻辑。

我在这里找到了一些东西 https://groups.google.com/forum/#!topic/scalaz/sHxFsFpE86c

或者说我可以使用Shapeless这样做 Folding a list of different types using Shapeless in Scala

它们太复杂了。我想要的是,在我定义逻辑之后,我该如何执行它?

希望我在此处提供足够的详细信息以获得答案。我真的很想学这个。谢谢

1 个答案:

答案 0 :(得分:1)

我稍微修改了你的代码,以创建一个独立的运行示例。我还使用Scalaz 7.2为您的问题添加了一个可能的答案,如何执行您的程序,Rúnar Bjarnason's ideas。 (我没有找到Scalaz中自然变换的or运算符,所以我在这里添加了它。)

我还添加了一些存根来为你的行为提供一些东西,并将你的服务简化为内部处理程序(因为我必须为两种语言组合创建一个新服务)。此外,我将Task.now{...}更改为Task{...}以创建异步任务,该任务在最后一行代码上执行。

以下是完整代码:

import scala.language.{higherKinds, implicitConversions}

import scalaz._
import scalaz.concurrent.Task

/* Stubs */

case class AccruePoints()
case class AccruePointModel(cmd: AccruePoints) {
  def toEvent(code: String, description: String): PointsAccruedEvent = PointsAccruedEvent(code, description)
}
case class PointsAccruedEvent(code: String, description: String)

/* Actions */

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]

/* Handlers */

object PointAccuralServiceHandler extends (ServiceAction ~> Task) {
  override def apply[A](fa: ServiceAction[A]): Task[A] = fa match {
    case ConsumeCommand(cmd) => {
      println("Service ConsumeCommand:" + cmd)
      Task(consume(cmd))
    }
    case CreateEvent(evt) => {
      println("Service CreateEvent:" + evt)
      Task(evt)
    }
  }

  def consume(cmd: AccruePoints): AccruePointModel =
    AccruePointModel(cmd)
}

case object LogServiceConsoleHandler extends (LogAction ~> Task) {
  override def apply[A](fa: LogAction[A]): Task[A] = fa match {
    case Info(m) =>
      Task(info(m))
    case Error(m) =>
      Task(error(m))
  }

  def info(msg: String): Unit = {
    println(s"INFO: $msg")
  }

  def error(msg: String): Unit = {
    println(s"ERROR: $msg")
  }
}

/* Execution */

class Service[F[_]](implicit I1: Inject[ServiceAction, F], I2: Inject[LogAction, F]) {
  def consume(cmd: AccruePoints): Free[F, AccruePointModel] = Free.liftF(I1(ConsumeCommand(cmd)))

  def emit(evt: PointsAccruedEvent): Free[F, PointsAccruedEvent] = Free.liftF(I1(CreateEvent(evt)))

  def info(msg: String): Free[F, Unit] = Free.liftF(I2(Info(msg)))

  def error(msg: String): Free[F, Unit] = Free.liftF(I2(Error(msg)))
}

object Service {
  implicit def instance[F[_]](implicit I1: Inject[ServiceAction, F], I2: Inject[LogAction, F]) = new Service[F]
}

def prg[F[_]](implicit service: Service[F]) = {
  val cmd = AccruePoints()
  for {
    _ <- service.info("Command: " + cmd)
    model <- service.consume(cmd)
    _ <- service.info("Model: " + model)
    evt <- service.emit(model.toEvent("200", "Event Sent"))
    _ <- service.info("Event:" + evt)
  } yield evt
}

type App[A] = Coproduct[ServiceAction, LogAction, A]

def or[F[_], G[_], H[_]](f: F ~> H, g: G ~> H) =
  new (({type t[x] = Coproduct[F, G, x]})#t ~> H) {
    override def apply[A](c: Coproduct[F, G, A]): H[A] = c.run match {
      case -\/(fa) => f(fa)
      case \/-(ga) => g(ga)
    }
  }

val app = prg[App]

val ret = app.foldMap(or(PointAccuralServiceHandler, LogServiceConsoleHandler))
ret.unsafePerformSync