2014-02-20 2 views
7

Данные классы Родитель и ребенок.Неявное разрешение с контравариантностью

scala> class Parent 
defined class Parent 

scala> class Child extends Parent 
defined class Child 

Определение implicits для Родителя и Ребенка

scala> implicit val a = new Parent 
a: Parent = [email protected] 

scala> implicit val b = new Child 
b: Child = [email protected] 

implicitly Используйте, чтобы выяснить, какие неявные получает решен.

scala> implicitly[Child] 
res1: Child = [email protected] 

иллюстрации моего понимания:

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

Теперь давайте использовать контравариантным типа.

scala> trait A[-T] 
defined trait A 

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

Затем определите класс родителя и ребенка.

scala> class Parent 
defined class Parent 

scala> class Kid extends Parent 
defined class Kid 

Создайте имплициты для них тоже.

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]] 
         ^

В первом примере (без контрвариации), Скал смог решить неявный Child для implicitly[Parent]. Мне кажется, что он выбирает самый низкий подтип.

Однако при использовании contravariance поведение изменяется. Зачем?

ответ

4

Ваши вложения напечатаны Concrete, и это инвариантно здесь.

Попробуйте либо

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

или

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

Больше слова:

Implicits (ценности или представления) должны иметь явный тип, так что вы никогда не удивлены выведенного типа. Выбор неявного - это все о типе.

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

Для простых значений (не вызовов функций), что сводится к соответствию или подтипированию.

Существует также правило, что определение в «производном типе» (обычно подклассе) является предпочтительным.

Вот тест, который вы можете сделать, используя только общедоступные бытовые материалы:

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:

Другой вид, почему это важно, какого типа вы тестируете:

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]] 
         ^

Дайте им явные типы, и дисперсия Бетона не имеет значения.Вы всегда предоставляете явные типы для своих имплицитов, не так ли? Как говорит нам ретроном?

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) 
+0

Не могли бы вы сказать больше, почему неизменность Concrete вызывает такое поведение? –

+2

Я добавил больше слов, упрощая 6.26.3 спецификации. (Упрощение, так что даже я могу это понять.) –

+0

Спасибо! Поскольку я называю «неявно [A [Kid]]», я понимаю, что 'A [-T]' является базовым типом, который является контравариантным. Итак, как сделать контравариант 'Conrete' влияющим на неявное разрешение? [Еще раз спасибо] –

Смежные вопросы