Scala: передача контравариантного типа в качестве неявного параметра не выбирает ближайший супертип? - PullRequest
0 голосов
/ 20 сентября 2018

Почему следующий код не принимает неявное значение 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" ???

Если вы сделаете TC инвариантным (то есть trait TC[T] (...)), тогда он будет работать нормально и doit(new B) возвращает "это B", как и ожидалось.

При добавлении еще одного неявного для типа 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" ???

И снова все будет хорошо, если TCявляется инвариантом.

Что здесь происходит и как это решить?Моя цель - иметь контравариант TC, который неявно выбирает ближайший подходящий супертип.

Ответы [ 2 ]

0 голосов
/ 20 сентября 2018

Поскольку 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
0 голосов
/ 20 сентября 2018

Если имеется несколько допустимых аргументов, которые соответствуют типу неявного параметра, наиболее специфичный будет выбран с использованием правил разрешения статической перегрузки.Если параметр имеет аргумент по умолчанию и неявный аргумент не найден, используется аргумент по умолчанию.

- Спецификация языка Scala - версия 2.12

В основном,поскольку вы делаете свой TypeClass контравариантным, это означает, что TC[Any] <:< TC[A] <:< TC[B] (где <: <означает подтип) </em>.И для этого TC[Any] считается наиболее конкретным.

...