Implicits and Variances

1 minute read

We have talked about Implicits1 and Variances2 detailedly, In this blog we will talk about an edge case when use them together.

Let’s say we have a data type called Data and we also have a syntax sugar to convert Any to Data

trait Data[A]
case class Data1[A](v:A) extends Data[A]
object Data{
    implicit def any2Data(v:Any):Data[Any] = Data1(v)
}

Then we want to use it like this

$ val a:Data[Int] = 1
cmd22.sc:5: type mismatch;
 found   : Int(1)
 required: ammonite.$sess.cmd22.Data[Int]
val a:Data[Int] = 1
                  ^
Compilation Failed

The implicit function any2Data doesn’t work.

How about we change Data to be Covariant?

trait Data[+A]
$ val a:Data[Int] = 1
cmd22.sc:5: type mismatch;
 found   : Int(1)
 required: ammonite.$sess.cmd22.Data[Int]
val a:Data[Int] = 1
                  ^
Compilation Failed

Oops, same error! Can’t we use the any implicit function on Int? Let’s try Contravariant

trait Data[-A]
$ val a:Data[Int] =1
a: Data[Int] = Data1(1)

It works! Actually compiler will always try to find the exact implicit function. In Covariant and Invariant, the Data[Any] can’t be treated as Data[Int], so compiler won’t use the implicit function. In Contravariant, Data[Any] is the child of Data[Int], so compiler can use the implicit function to fix the compile error.

In practice we won’t define the implicit function like the previous example, the following code is a better way

object Data{
    implicit def a2Data[A](v:A):Data[A] = Data1(v)
}

References

Tags:

Updated:

Comments