如何组合 Kleisli[M, A, C] 和 Kleisli[M, B, C]

How to combine Kleisli[M, A, C] and Kleisli[M, B, C]

我遵循优秀书籍 Reactive Domain Modeling 的设计,我需要将 Kleisli 与不同类型混合使用:

object CombinedKleisli {
  type User = String
  type Project = String

  trait UserRepo
  trait ProjectRepo
  trait UserService {
    def findByUserId : Kleisli[Future, UserRepo, User]
  }
  trait ProjectService {
    def findProjectById : Kleisli[Future, ProjectRepo, Project]
  }
  trait ComposedService extends UserService with ProjectService {
    for {
      user <- findByUserId
      project <- findProjectById
    } yield (user, project)
  }

}

由于类型不一致,我得到以下编译错误

Error:(28, 15) type mismatch;
 found   : scalaz.Kleisli[scala.concurrent.Future,domain.service.ServiceTest.ProjectRepo,(domain.service.ServiceTest.User, domain.service.ServiceTest.Project)]
    (which expands to)  scalaz.Kleisli[scala.concurrent.Future,domain.service.ServiceTest.ProjectRepo,(String, String)]
 required: scalaz.Kleisli[scala.concurrent.Future,domain.service.ServiceTest.UserRepo,?]
      project <- findProjectById
              ^

解决这个问题的最佳方法是什么,创建一个

trait Context {
  def userRepo
  def projectRepo
}

并用它污染 UserServiceProjectService ?

您需要想出一些方法将输入类型组合成一个类型。实现此目的的一种方法是继承——您将拥有一个类型 UserRepo with ProjectRepo,它是 UserRepoProjectRepo 的子类。另一种方法是组合,你有一个元组 (UserRepo, ProjectRepo).

在这两种情况下,您通常会使用 local 到 "expand" 每个箭头的输入类型,以便您可以在 for-comprehension 中组合它们:

for {
  user <- findByUserId.local[(UserRepo, ProjectRepo)](_._1)
  project <- findProjectById.local[(UserRepo, ProjectRepo)](_._2)
} yield (user, project)

这里的(UserRepo, ProjectRepo)类型参数参数指定了新的输入类型,值参数(例如_._1)指定了如何从新的输入类型得到原始箭头的输入类型。