Ковариантный тип А встречается в контравариантном положении в типе А значения а

У меня есть следующий класс:

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)