Search code examples
scalatypeclassimplicitpath-dependent-typetype-members

resolve an implicit then use the type stored in it to resolve a second implicit


I'm trying to resolve an implicit then use the type stored in it to resolve a second implicit.
Here is the code:

sealed trait ReturnTypeRetriever[T] {
  type ReturnType
}
object ReturnTypeRetrievers {
  implicit val expl = new ReturnTypeRetriever[ExplorationStreamFilter] {
    type ReturnType = SimpleFilter[Context, Context]
  }
}

sealed trait Retriever[T, +R] {
  def get(config: Config): R //SimpleFilter[Context, Context] 
}
object Retrievers {
  // implementation here for T=ExplorationStreamFilter and R=SimpleFilter[Context, Context]
  implicit val expl = new Retriever[ExplorationStreamFilter, SimpleFilter[Context, Context]] {
    override def get(config: Config) = {..}
  }

  // putting it all together
  def getOrEmpty[A](config: Config)(implicit evret: ReturnTypeRetriever[A]) = {
    val ev = implicitly[Retriever[A, evret.ReturnType]]  <-- ERROR 1: cannot find implicit defined above ^
    ev.get(config)
  }
}

Calling it like this:

lazy val exploration = getOrEmpty[ExplorationStreamFilter](config) <--- ERROR 2: cannot find implicit here either

Compiling shows 2 errors:

ERROR 1 message: could not find implicit value for parameter e: Retriever[A,evret.ReturnType]
[error]   val ev = implicitly[Retriever[A, evret.ReturnType]]

ERROR 2 message: could not find implicit value for parameter evret: ReturnTypeRetriever[ExplorationStreamFilter]
[error]   lazy val exploration = getOrEmpty[ExplorationStreamFilter](config)

Why can't the compiler find the implicit? What's a solution for this?


Solution

  • This seems to work.
    It uses the Aux pattern, and the Partially-applied trick.

    sealed trait ReturnTypeRetriever[T] {
      type ReturnType
    }
    
    // It should be named equally to the trait, in order to be a companion.
    // That way, implicits will be in scope.
    object ReturnTypeRetriever {
      // Aux pattern!
      // Used to transform a type member, into a type parameter, for better inference.
      type Aux[T, R] = ReturnTypeRetriever[T] { type ReturnType = R }
    
      // Implicits must always have explicit type signatures.
      implicit final val expl: Aux[ExplorationStreamFilter, SimpleFilter[Context, Context]] =
        new ReturnTypeRetriever[ExplorationStreamFilter] {
          override final type ReturnType = SimpleFilter[Context, Context]
        }
    }
    
    sealed trait Retriever[T, +R] {
      def get(config: Config): R
    }
    
    object Retriever {
      implicit final val expl: Retriever[ExplorationStreamFilter, SimpleFilter[Context, Context]] =
        new Retriever[ExplorationStreamFilter, SimpleFilter[Context, Context]] {
          override final def get(config: Config): SimpleFilter[Context, Context] =
            ???
        }
    
      // Ideally, you should make this class private[package]
      // Where package is the package in which this is defined.      
      final class GetOrEmptyPartiallyApplied[A](private val dummy: Boolean) extends AnyVal {
        def apply[R](config: Config)
                    (implicit rtr: ReturnTypeRetriever.Aux[A, R], retriever: Retriever[A, R]): R =
          retriever.get(config)
      }
    
      // Partially-applied trick!
      // Used to allow partial application of a type.
      // User only needs to specify A, the compiler will infer R.
      def getOrEmpty[A]: GetOrEmptyPartiallyApplied[A] =
        new GetOrEmptyPartiallyApplied(dummy = true)
    }
    

    (I do not understand what is the purpose of the ReturnTypeRetriever trait. But I leave it to preserve the original problem)