Я изо всех сил пытаюсь понять ковариантность Scala в сочетании с нижними границами. Я проиллюстрирую свое замешательство в следующем фрагменте кода с двумя ошибками компиляции.
class Queue[+T]:
def enqueue[U >: T](x: U): Queue[U] = null
class IntQueue extends Queue[Int]:
override def enqueue[Int](x: Int): Queue[Int] =
println(math.sqrt(x)) // 1) Found: (x : Int) Required: Double
super.enqueue(x) // 2) Found: Queue[Any] Required: Queue[Int]
Во-первых, есть общий класс Queue
, который принимает один параметр типа с аннотацией ковариации +
. Это нормально, так как я хочу выполнять такие задания, как val a: Queue[Any] = IntQueue()
. Его метод enqueue
имеет нижнюю границу параметра типа U >: T
. Это необходимо, потому что в противном случае x
был бы в контравариантном положении, допуская неприятные вещи, подобные ArrayStoreException
в Array
в Java.
Во-вторых, существует параметризованный класс IntQueue
, сгенерированный Queue
с определенным типом Int
.
Вопросы - почему возникают ошибки компилятора 1) и 2)
объявление 1)
math.sqrt
определяется как def sqrt(x: Double): Double
. Тип моего аргумента x
относится к типу Int
. В этом случае должно произойти неявное преобразование Int->Long->Float->Double
. Почему компилятор не выполняет неявное преобразование?Включить
Правильным переопределением для T=Int
будет scastie.scala-lang.org/DmytroMitin/h1aJbMcATGmIF4XrfD3bTg/2 Теперь ошибка Found: (x : U); Required: Double
В вашем определении переопределяющего метода Int
— это не то, что вы ожидаете. На самом деле вы определяете параметр типа с именем Int
, но не ссылаетесь на «исходный» тип Int
и, таким образом, «скрываете» исходный тип Int
.
Вы на самом деле написали то же самое, что и:
override def enqueue[I](x: I): Queue[I] = ...
Объявите его как метод без переопределения для достижения того, что вы ожидаете:
def enqueue(x: Int): Queue[Int] = ...
поэтому, если я правильно понимаю, вы неявно указываете, какой тип параметра U
находится внутри IntQueue.enqueue
, объявляя тип его аргумента — x: Int
Вы только неявно определяете это U >: Int
, но вы не можете определить что-то более сильное, что нарушило бы контракт Queue
, который заключается в том, что вы можете поставить в очередь любой другой элемент с супертипом Int
. Вот почему вы не можете определить этот метод с помощью override
. Это должен быть другой метод.
вы можете определить метод с помощью override
: override def enqueue[U >: Int](x: U): Queue[U]
компилируется просто отлично
@Talos, да, вы можете, но это не то, что вы ожидали в первую очередь: метод можно вызвать с чем угодно, что является супертипом Int
, и вы вернете QUeue[U]
, а не IntQUeue
.
Поскольку
IntQueue
может быть поднят какQueue[Any]
, вы должны иметь возможность получать что угодно, не толькоInt
, наenqueue
и, возможно, возвращать что-то отличное отIntQueue