scalafunctional-programmingscalazkleisli

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


I follow the design of the excellent book Reactive Domain Modeling and I need to mix Kleisli with different types :

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

}

And as the types don't align, I get the following compilation error

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
              ^

What is the best way to fix that, creating a

trait Context {
  def userRepo
  def projectRepo
}

and pollute UserService and ProjectService with it ?


Solution

  • You'll need to come up with some way to combine the input types into a single type. One way to do this would be inheritance—you'd have a type UserRepo with ProjectRepo that is a subclass of both UserRepo and ProjectRepo. Another way would be composition, where you have a tuple (UserRepo, ProjectRepo).

    In both cases you'd generally use local to "expand" the input types of each arrow so that you can compose them in the for-comprehension:

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

    Here the (UserRepo, ProjectRepo) type parameter argument specifies the new input type and the value argument (e.g. _._1) specifies how to get to the original arrow's input type from the new input type.