Ковариантный тип А встречается в контравариантном положении в типе А значения а
У меня есть следующий класс:
case class Box[+A](value: A) {
def set(a: A): Box[A] = Box(a)
}
И компилятор жалуется:
Error:(4, 11) covariant type A occurs in contravariant position in type A of value a
def set(a: A): Box[A] = Box(a)
Я много искал ошибку, но не мог найти что-то полезное, чтобы
помогите мне понять ошибку.
Может кто-нибудь объяснить, почему возникает ошибка?
Ответы
Ответ 1
Сообщение об ошибке на самом деле очень ясно, как только вы это понимаете. Пойдем туда вместе.
Вы объявляете класс Box
как ковариантный в своем параметре типа A
. Это означает, что для любого типа X
, продолжающегося A
(т.е. X <: A
), Box[X]
можно рассматривать как Box[A]
.
Чтобы дать ясный пример, рассмотрим тип Animal
:
sealed abstract class Animal
case class Cat extends Animal
case class Dog extends Animal
Если вы определяете Dog <: Animal
и Cat <: Animal
, то как Box[Dog]
, так и Box[Cat]
можно увидеть как Box[Animal]
, и вы можете, например, создайте единый набор, содержащий оба типа, и сохраните тип Box[Animal]
.
Хотя это свойство может быть очень удобным в некоторых случаях, оно также накладывает ограничения на операции, которые вы можете сделать на Box
. Вот почему компилятор не позволяет вам определить def set
.
Если вы разрешаете определять
def set(a:A): Unit
тогда я допустим следующий код:
val catBox = new Box[Cat]
val animalBox: Box[Animal] = catBox // valid because `Cat <: Animal`
val dog = new Dog
animalBox.set(dog) // This is non-sensical!
Последняя строка, очевидно, является проблемой, потому что catBox
теперь будет содержать Dog
! Аргументы метода появляются в так называемой "контравариантной позиции", что является противоположностью ковариации. Действительно, если вы определяете Box[-A]
, то Cat <: Animal
подразумевает Box[Cat] >: Box[Animal]
(Box[Cat]
является супертипом Box[Animal]
). Для нашего примера это, конечно, нечувствительно.
Одно из решений вашей проблемы состоит в том, чтобы сделать класс Box
неизменным (т.е. не предоставлять какой-либо способ изменить содержимое Box
), а вместо этого использовать метод apply define в вашем компаньоне case class
для создания новые коробки. Если вам нужно, вы также можете определить set
локально и не выставлять его где-либо вне Box
, объявив его как private[this]
. Компилятор допустит это, потому что private[this]
гарантирует, что последняя строка нашего ошибочного примера не будет компилироваться, поскольку метод set
полностью невидим за пределами определенного экземпляра Box
.
Ответ 2
Другие уже дали ответ, почему код не компилируется, но они не дали решения о том, как скомпилировать код:
> case class Box[+A](v: A) { def set[B >: A](a: B) = Box(a) }
defined class Box
> trait Animal; case class Cat() extends Animal
defined trait Animal
defined class Cat
> Box(Cat()).set(new Animal{})
res4: Box[Animal] = Box([email protected])
> Box[Cat](Cat()).set[Animal](new Animal{})
res5: Box[Animal] = Box([email protected])
Аргумент типа B >: A
- это нижняя граница, которая сообщает компилятору, если необходимо, при необходимости сделать супертип. Как видно из примера, Animal
выводится, когда задано Cat
.
Ответ 3
Попытайтесь понять, что означает, что ваш Box[+A]
будет ковариантным в A
:
Это означает, что a Box[Dog]
также должен быть Box[Animal]
, поэтому любой экземпляр Box[Dog]
должен иметь все методы a Box[Animal]
.
В частности, a Box[Dog]
должен иметь метод
set(a: Animal): Box[Animal]
Однако он имеет только метод
set(a: Dog): Box[Dog]
Теперь вы могли бы предположить, что вы можете сделать первый из второго, но это не так: я хочу разместить Cat
, используя только вторую подпись? Это не выполнимо и то, что говорит компилятор: параметр в методе - это контравариантная позиция (вы можете вводить только контравариантные (или инвариантные) параметры типа).
Ответ 4
В принципе, вы не можете положить A
, если A
является ковариантным, вы можете его вынуть (например: return A
). Если вы хотите поместить A
внутрь, тогда вам нужно сделать его contravariant
.
case class Box[-A](value: A)
Вы хотите сделать то и другое, а затем просто сделать его инвариантным
case class Box[A](value: A)
Лучше всего держать его в коварианте и избавиться от сеттера и пойти на непреложный подход.
Ответ 5
в дополнение к другим anwsers я хотел бы предложить другой подход:
def set[B >: A](x: B): Box[B] = Box(x)