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 ?
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.