Search code examples
scalaplayframeworkguice

How to bind a class that extends a Trait with a monadic type parameter using Scala Guice?


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?


Solution

  • 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).