Поскольку 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)
Теперь он выбирает наиболее конкретный во всех случаях:
it's A
it's A
it's C
it's C
Обходной путь 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)
prints
it's A
it's B