Search code examples
scalaimplicitpath-dependent-type

Scala: implicit lookup of type class instance for path-dependent type


trait Encoder[From, To] {
  def encode(x: From): To
}
object Encoder {
  implicit val thingToString: Encoder[Thing, String] = new Encoder[Thing, String] {
    def encode(x: Thing): String = x.toString
  }
}

trait Config {
  type Repr
}
class MyConfig extends Config { type Repr = String }
//class ConcreteConfig { type Repr = String }

class Api[T](val config: Config) {
  def doSomething(value: T)(implicit encoder: Encoder[T, config.Repr]): Unit = {}
}

case class Thing(a: Int)

object Test extends App {
  import Encoder._

  val api = new Api[Thing](new MyConfig)
  api.doSomething(Thing(42))
}

The call to api.doSomething fails to compile:

could not find implicit value for parameter encoder: Encoder[Thing,Test.api.config.Repr]

If I change the signature of class Api[T]'s constructor so that it takes a ConcreteConfig, then the compiler can tell that config.Repr == String and the implicit lookup succeeds. But this wouldn't really work for my use case.

Is there any other way to guide the implicit lookup? Am I losing type info because I'm missing a type refinement or something?


Solution

  • This can't be achieved since config.Repr is not a stable path. The compiler can't determine that config.Repr = String, even though the runtime value of config is of type MyConfig.

    This is going to work only if you can provide concrete config instance as a type parameter in Api, which will tell compiler what is the exact type of Repr:

    class Api[T, C <: Config](val config: C) {
      def doSomething(value: T)(implicit encoder: Encoder[T, config.Repr]): Unit = {}
    }
    
    object Test extends App {
      import Encoder._
    
      val api = new Api[Thing, MyConfig](new MyConfig)
      api.doSomething(Thing(42))
    }