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

Yan*_*san 4 functional-programming scala scalaz kleisli

我遵循了优秀书籍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)
  }

}
Run Code Online (Sandbox Code Playgroud)

由于类型不对齐,我收到以下编译错误

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
              ^
Run Code Online (Sandbox Code Playgroud)

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

trait Context {
  def userRepo
  def projectRepo
}
Run Code Online (Sandbox Code Playgroud)

和污染UserServiceProjectService它?

Tra*_*own 5

您需要想出某种方法将输入类型合并为一个类型。这样做将是一个方法的继承-你会拥有一种UserRepo with ProjectRepo是两者的子类UserRepoProjectRepo。另一种方法是组合,你有一个 tuple (UserRepo, ProjectRepo)

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

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

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