2015-05-21 2 views
2

Вот некоторый код из this урока:Почему входные параметры контравариантны в методах?

case class ListNode[+T](h: T, t: ListNode[T]) { 
    def head: T = h 
    def tail: ListNode[T] = t 
    def prepend(elem: T): ListNode[T] = 
    ListNode(elem, this) 
} 

В учебнике говорится:

К сожалению, эта программа не компилируется, так как ковариация аннотаций возможно только, если тип переменной только в используется ковариантные позиции. Поскольку переменная типа T появляется как тип параметра метода prepend, это правило нарушается.

Как T не в ковариантной позиции в predend и другие T ссылки (def head: T = h и def tail: ListNode[T] = t), по-видимому, ковариантны?

Что я спрашиваю, почему T в prepend не ковариант. Это, конечно, не описано в Why is Function[-A1,...,+B] not about allowing any supertypes as parameters?, что, как мне кажется, было направлено другими.

+1

Это как написано. В двух других методах 'T' находится в контравариантном положении, это _only_ в возвращаемом типе, тогда как для' preend' он также находится в параметре ('elem: T'), поэтому он может быть только инвариантным. –

+0

Рассмотрим следующий пример: 'trait Fruit; класс Apple расширяет Fruit; класс Груша расширяет Фрукты. Предположим, что 'ListNode' является ковариантным, поэтому' ListNode [Apple] 'также является' ListNode [Fruit] ':' val node: ListNode [Fruit] = ListNode [Apple] (новый Apple(), null) '. Теперь, если вы 'prepend' с другим' Fruit', 'node.prepend (new Pear())', вы явно не получите (typeafe) 'ListNode [Apple]'. –

ответ

4

Входные параметры методов не являются в ковариантных позициях, но контравариантные позиции. Только возвращаемые типы методов находятся в ковариантных положениях.

Если Defination из ListNode класса было ОК, тогда я мог бы написать такой код:

val list1: ListNode[String] = ListNode("abc", null) 
val list2: ListNode[Any] = list1 // list2 and list1 are the same thing 
val list3: ListNode[Int] = list2.prepend(1) // def prepend(elem: T): ListNode[T] 
val x: Int = list3.tail.head // ERROR, x in fact is "abc" 

Престол, если аргументы были общековариантной позицией, то контейнер всегда может содержать значения другого типа который имеет тот же самый предок своего реального типа, и это определенно WRONG!

Так, обращаясь к исходному коду scala.collection.immutable.List.::, ваш класс должен быть определен как это:

case class ListNode[+T](h: T, t: ListNode[T]) { 
    def head: T = h 
    def tail: ListNode[T] = t 
    def prepend[A >: T](elem: A): ListNode[A] = ListNode(elem, this) 
} 

Аргумент типа A новый параметр типа, который ниже, ограничен в T.

+0

отличное объяснение – Aamir

1

Пример:

val dogList = ListNode[Dog](...) 
val animal = Animal() 
val dog = Dog() 

dogList.prepend(dog) //works 
dogList.prepend(animal) //fails 

ковариация означает, что вы можете использовать ListNode [собака] как ListNode [Животное]

но:

//if compiler would allow it 
val animalList: ListNode[Animal] = dogList 
animalList.prepend(dog) //works 
animalList.prepend(animal) //would fail, but you expect an animallist to accept any animal 
Смежные вопросы