Implicit resolution with contravariance

These classes are Parent and Child.

scala> class Parent defined class Parent scala> class Child extends Parent defined class Child 

Define implications for parent and child

 scala> implicit val a = new Parent a: Parent = Parent@5902f207 scala> implicit val b = new Child b: Child = Child@3f7d8bac 

Use implicitly to find out what implicit solutions will be allowed.

 scala> implicitly[Child] res1: Child = Child@3f7d8bac 

Illustration of my understanding:

  Parent | Child -- implicit resolution gets the most specific, lowest sub-type 

Now you can use the contravariant type.

 scala> trait A[-T] defined trait A scala> case class Concrete[T]() extends A[T] defined class Concrete 

Then define the parent and child classes.

 scala> class Parent defined class Parent scala> class Kid extends Parent defined class Kid 

Create implications for them.

 scala> implicit val x = Concrete[Parent] x: Concrete[Parent] = Concrete() scala> implicit val y = Concrete[Kid] y: Concrete[Kid] = Concrete() scala> implicitly[A[Parent]] res1: A[Parent] = Concrete() scala> implicitly[A[Kid]] <console>:21: error: ambiguous implicit values: both value x of type => Concrete[Parent] and value y of type => Concrete[Kid] match expected type A[Kid] implicitly[A[Kid]] ^ 

In the first example (without contravariance), Scala succeeded in resolving the implicit Child for implicitly[Parent] . It seems to me that he chooses the lowest subtype.

However, when using contravariance behavior changes. Why?

+7
scala
source share
1 answer

Your implicits are printed by Concrete , and it is invariant here.

Try either

 case class Concrete[-T]() extends A[T] 

or

 implicit val x: A[Parent] = Concrete[Parent] 

More words:

Implicits (values ​​or representations) must be of an explicit type so that you are never surprised at the type being inferred. Implicit selection is all about type.

He chooses one of your implications using the same rules as the overload resolution conversion, which is used to select alternatives for the overloaded character.

For simple values ​​(not function calls) that boil down to matching or subtyping.

There is also a rule that a definition in a "derived type" (usually a subclass) is preferred.

Here is a test you can do using only commonly available household materials:

 scala> :power ** Power User mode enabled - BEEP WHIR GYVE ** ** :phase has been set to 'typer'. ** ** scala.tools.nsc._ has been imported ** ** global._, definitions._ also imported ** ** Try :help, :vals, power.<tab> ** scala> trait A[-T] defined trait A scala> case class Concrete[T](i: Int) extends A[T] defined class Concrete scala> class Parent ; class Kid extends Parent defined class Parent defined class Kid // it will pick X if X isAsSpecific as Y but not conversely scala> typer.infer.isAsSpecific(typeOf[Concrete[Kid]],typeOf[Concrete[Parent]]) res0: Boolean = false scala> typer.infer.isAsSpecific(typeOf[Concrete[Parent]],typeOf[Concrete[Kid]]) res1: Boolean = false scala> case class Concrete[-T](i: Int) extends A[T] defined class Concrete scala> typer.infer.isAsSpecific(typeOf[Concrete[Kid]],typeOf[Concrete[Parent]]) res2: Boolean = false scala> typer.infer.isAsSpecific(typeOf[Concrete[Parent]],typeOf[Concrete[Kid]]) res3: Boolean = true 

Edit:

Another look at why this is important, what type you are testing:

 scala> trait A[-T] defined trait A scala> case class Concrete[T](i: Int) extends A[T] // invariant defined class Concrete scala> class Parent ; class Kid extends Parent defined class Parent defined class Kid scala> implicitly[Concrete[Parent] <:< Concrete[Kid]] <console>:13: error: Cannot prove that Concrete[Parent] <:< Concrete[Kid]. implicitly[Concrete[Parent] <:< Concrete[Kid]] ^ scala> implicit val x: Concrete[Parent] = Concrete[Parent](3) // the inferred type x: Concrete[Parent] = Concrete(3) scala> implicit val y = Concrete[Kid](4) y: Concrete[Kid] = Concrete(4) // both values conform to A[Kid] (because A is contravariant) // but when it puts x and y side-by-side to see which is more specific, // it no longer cares that you were looking for an A. All it knows is // that the values are Concrete. The same thing happens when you overload // a method; if there are two candidates, it doesn't care what the expected // type is at the call site or how many args you passed. scala> implicitly[A[Kid]] <console>:15: error: ambiguous implicit values: both value x of type => Concrete[Parent] and value y of type => Concrete[Kid] match expected type A[Kid] implicitly[A[Kid]] ^ 

Give them explicit types, and Concrete variance does not matter. You always provide explicit types for your implications, right? Just as the retron tells us?

 scala> implicit val x: A[Parent] = Concrete[Parent](3) x: A[Parent] = Concrete(3) scala> implicit val y: A[Kid] = Concrete[Kid](4) y: A[Kid] = Concrete(4) scala> implicitly[A[Kid]] res2: A[Kid] = Concrete(3) 
+4
source share

All Articles