Implicit Resolution with Contravariance
Given classes Parent and Child.
scala> class Parent
defined class Parent
scala> class Child extends Parent
defined class Child
Define implicits 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 which implicit gets resolved.
scala> implicitly[Child]
res1: Child = Child@3f7d8bac
illustration of my understanding:
Parent
|
Child -- implicit resolution gets the most specific, lowest sub-type
Now, let's use a contravariant type.
scala> trait A[-T]
defined trait A
scala> case class Concrete[T]() extends A[T]
defined class Concrete
Then define a Parent and Child class.
scala> class Parent
defined class Parent
scala> class Kid extends Parent
defined class Kid
Create implicits for them too.
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 was able to resolve the implicit Child
for implicitly[Parent]
. It seems to me that it's picking the lowest sub-type.
However, when using contravariance
, the behavior changes. Why?
Your implicits are typed Concrete
and that 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 views) should have an explicit type so you're never surprised by the inferred type. Picking the implicit is all about the type.
It picks one of your implicits using the same rules as the overloading resolution conversion that is used to pick alternatives of an overloaded symbol.
For simple values (not function calls), that comes down to conformance or subtyping.
There is also the 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 view of why it matters what type you're 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 the variance of Concrete won't matter. You always supply explicit types for your implicits, right? Just like retronym tells us to?
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)
链接地址: http://www.djcxy.com/p/77576.html
上一篇: DacPac排除用户并登录导出或导入
下一篇: 隐式分辨与逆变