cub*_*uce 4 scala implicit contravariance
为什么下面的代码不选择最接近的超类型的隐式val?
class A
class B extends A
trait TC[-T] { def show(t: T): String }
implicit val showA = new TC[A] { def show(a: A): String = "it's A" }
implicit val showB = new TC[B] { def show(b: B): String = "it's B" }
def doit[X](x: X)(implicit tc: TC[X]): Unit = println(tc.show(x))
doit(new A) // "it's A" as expected
doit(new B) // "it's A" ... why does this not give "it's B" ???
Run Code Online (Sandbox Code Playgroud)
如果使TC不变(即trait TC[T] (...)),则它可以正常工作doit(new B)并按预期返回“它是B”。
通过为type添加另一个隐式Any,这个问题更加严重:
class A
class B extends A
trait TC[-T] { def show(t: T): String }
implicit val showA = new TC[A] { def show(a: A): String = "it's A" }
implicit val showB = new TC[B] { def show(b: B): String = "it's B" }
implicit val showAny = new TC[Any] { def show(x: Any): String = "it's Any" }
def doit[X](x: X)(implicit tc: TC[X]): Unit = println(tc.show(x))
doit(new A) // "it's Any" ... why does this not give "it's A" ???
doit(new B) // "it's Any" ... why does this not give "it's B" ???
Run Code Online (Sandbox Code Playgroud)
再说一次,如果TC不变的话,就可以了。
这是怎么回事,以及如何解决?我的目标是要有一个TC隐式选择最适合的超类型的逆变。
由于TC[-T]在类型参数上是互变的,TC[A]是的子类型TC[B],因此被认为是更“特定的”。这是一个众所周知的(且颇有争议的)设计决策,这实际上意味着具有协变性的隐式分辨率有时表现得非常出乎意料。
解决方法1:使用继承对隐式进行优先级排序
这是使用继承和“ LowPriority-*-Implicits”模式的方法:
class A
class B extends A
class C extends B
class D extends C
trait TC[-T] { def show(t: T): String }
trait LowPriorityFallbackImplicits {
implicit def showA[X <: A]: TC[X] =
new TC[A] { def show(a: A): String = "it's A" }
}
object TcImplicits extends LowPriorityFallbackImplicits {
implicit def showC[X <: C]: TC[X] =
new TC[C] { def show(c: C): String = "it's C" }
}
def doit[X](x: X)(implicit tc: TC[X]): Unit = println(tc.show(x))
import TcImplicits._
doit(new A)
doit(new B)
doit(new C)
doit(new D)
Run Code Online (Sandbox Code Playgroud)
现在,它在所有情况下都选择最具体的一种:
it's A
it's A
it's C
it's C
Run Code Online (Sandbox Code Playgroud)
解决方法2:不变的助手特性
这是通过引入类型参数中不变的辅助特征来强制交换特定示例中的隐式的方法:
class A
class B extends A
trait TC[-T] { def show(t: T): String }
val showA = new TC[A] { def show(a: A): String = "it's A" }
val showB = new TC[B] { def show(b: B): String = "it's B" }
trait TcImplicit[X] { def get: TC[X] }
implicit val showAImplicit = new TcImplicit[A] { def get = showA }
implicit val showBImplicit = new TcImplicit[B] { def get = showB }
def doit[X](x: X)(implicit tc: TcImplicit[X]): Unit = println(tc.get.show(x))
doit(new A)
doit(new B)
Run Code Online (Sandbox Code Playgroud)
版画
it's A
it's B
Run Code Online (Sandbox Code Playgroud)