I need to bind the implementation of this trait:
trait ClientRepository[F[_]] {
def list(): F[Iterable[ClientDTO]]
}
to this implementation:
import cats.effect.IO
@Singleton
class ClientRepositoryImpl @Inject()(db: OldDataBase, c: IOContextShift)
extends ClientRepository[IO] {
override def list(): IO[Iterable[ClientDTO]] = ???
}
I'm using Scala Play! v2.7.2 and Scala v2.12.8, with scala-guice
v4.2.1. In order to bind the trait to its implementation I would like to do something like that in my Module.scala
:
class Module(environment: Environment, configuration: Configuration)
extends AbstractModule
with ScalaModule {
override def configure() = {
bind[ClientRepository].to[ClientRepositoryImpl[IO]].in[Singleton]
}
}
And the error I get is:
[error] app/Module.scala:37:9: kinds of the type arguments (ClientRepository) do not conform to the expected kinds of the type parameters (type T).
[error] ClientRepository's type parameters do not match type T's expected parameters:
[error] trait ClientRepository has one type parameter, but type T has none
[error] bind[ClientRepository].to[ClientRepositoryImpl[IO]].in[Singleton]
[error] ^
[error] app/Module.scala:37:31: ClientRepositoryImpl does not take type parameters
[error] bind[ClientRepository].to[ClientRepositoryImpl[IO]].in[Singleton]
[error] ^
[error]
I've also tried:
bind[ClientRepository[IO]].to[ClientRepositoryImpl].in[Singleton]
Module.scala:37:9: kinds of the type arguments (cats.effect.IO) do not conform to the expected kinds of the type parameters (type T).
[error] cats.effect.IO's type parameters do not match type T's expected parameters:
[error] class IO has one type parameter, but type T has none
[error] bind[ClientRepository[IO]].to[ClientRepositoryImpl].in[Singleton]
[error] ^
and bind[ClientRepository[IO[_]]].to[ClientRepositoryImpl].in[Singleton]
Module.scala:37:27: cats.effect.IO[_] takes no type parameters, expected: one
[error] bind[ClientRepository[IO[_]]].to[ClientRepositoryImpl].in[Singleton]
[error] ^
What's the correct way to fix this?
I found the proper solution using Guice's TypeLiteral, after reading this SO answer and this one.
The working solution is:
// In Module.scala configure()
bind(new TypeLiteral[ClientRepository[IO]] {}).to(classOf[ClientRepositoryImpl])
because we must provide a class that can be instantiated (with a type parameter, that in our case is IO
). TypeLiteral
, which is a special class that enables you to specify a full parameterized type, can be used to create the actual binding to a particular implementation of our Repo[F[_]]
. A class with a generic parameter cannot be instantiated but we can force Guice to pick up a specific ClientRepository
that has been constructed with the type parameter cats.effect.IO
.
Last but not least whenever you have to inject the trait ClientRepository
you have to specify the type parameter as well. For instance:
class ClientResourceHandler @Inject()(
routerProvider: Provider[ClientRouter],
clientRepository: ClientRepository[IO]
)
the ClientResourceHandler
needs to call the repo, so we're injecting it using the trait ClientRepository[IO]
(not just ClientRepository
).