Search code examples
scalatype-inferenceimplicit-conversionimplicit

Type inference of implicit conversions


Consider this code:

sealed trait Data
case class StringData(string: String) extends Data
case class IntData(int: Int) extends Data

trait Reader[A] {
  def read(data: Data): A
}

implicit val stringReader: Reader[String] = {
  case StringData(string) => string
  case _                  => sys.error("not a string")
}
implicit val intReader: Reader[Int] = {
  case IntData(int) => int
  case _            => sys.error("not an int")
}

With this in scope, I want to write an implicit method that silently converts from Data values to their "real" Scala values.

implicit def fromData[A: Reader](data: Data): A =
  implicitly[Reader[A]].read(data)

But then, this code does not compile:

val str: String = StringData("foo")
val int: Int = IntData(420)

The error is a type mismatch. Standard debugging methods for implicits show that the A from fromData can't be infered (all implicit Readers are shown as applicable).

For your convenience, this is a link to a scastie of the code. In this other scastie, a similiar, yet different, and working snippet is presented.

My question: What is going on here?


Solution

  • Making the change to your Data class as well as your reader implicit conversion as below allows the code to compile.

    import scala.language.implicitConversions
    
    sealed trait Data[A]
    case class StringData(string: String) extends Data[String]
    case class IntData(int: Int) extends Data[Int]
    
    trait Reader[A] {
      def read(data: Data[A]): A
    }
    
    implicit val stringReader: Reader[String] = {
      case StringData(string) => string
      case _                  => sys.error("not a string")
    }
    implicit val intReader: Reader[Int] = {
      case IntData(int) => int
      case _            => sys.error("not an int")
    }
    
    implicit def fromData[A](data: Data[A])(implicit ev: Reader[A]): A = ev.read(data)
    
    val str: String = StringData("foo")
    val int: Int = IntData(420)
    

    You need data to be typed so that the compiler can infer which Reader should be used based on the type parameter of Data. Also notice that the following would not compile if you did not define an implicit Reader.

    case class DoubleData(d: Double) extends Data[Double]
    
    // Fails compilation because no implicit Reader exists.
    val d: Double = DoubleData(5d)