Почему параметр находится в контравариантном положении?

Я пытаюсь использовать параметр ковариантного типа внутри признака для создания case-класса следующим образом:

trait MyTrait[+T] {
  private case class MyClass(c: T)
}

компилятор говорит:

error: covariant type T occurs in contravariant position in type T of value c

Затем я попробовал следующее, но это также не сработало:

trait MyTrait[+T] {
  private case class MyClass[U <: T](c: U)
}

ошибка на этот раз:

error: covariant type T occurs in contravariant position in type >: Nothing <: T of type U

Может ли кто-нибудь объяснить, почему T здесь находится в ковариантном положении, и предложить решение этой проблемы? Спасибо!


person lapislazuli    schedule 08.03.2012    source источник
comment
Не могли бы вы объяснить, что вы действительно хотите сделать? Почему вы хотите, чтобы T был ковариантным, а не инвариантным?   -  person Daniel Martin    schedule 08.03.2012


Ответы (2)


Это фундаментальная особенность объектно-ориентированного программирования, которой не уделяется должного внимания.

Предположим, у вас есть коллекция C[+T]. Что означает +T, так это то, что если U <: T, то C[U] <: C[T]. Справедливо. Но что значит быть подклассом? Это означает, что должен работать каждый метод, работавший с исходным классом. Итак, предположим, у вас есть метод m(t: T). Это говорит о том, что вы можете взять любое t и что-то с ним сделать. Но C[U] может делать что-то только с U, а это может быть не все T! Таким образом, вы сразу опровергли свое утверждение, что C[U] является подклассом C[T]. Это нет. Есть вещи, которые можно сделать с C[T], но нельзя сделать с C[U].

Теперь, как вы обойти это?

Один из вариантов — сделать класс инвариантным (отбросить +). Другой вариант заключается в том, что если вы принимаете параметр метода, чтобы также разрешить любой суперкласс: m[S >: T](s: S). Теперь, если T изменится на U, ничего страшного: суперкласс T также является суперклассом U, и метод будет работать. (Однако вам придется изменить свой метод, чтобы иметь возможность обрабатывать такие вещи.)

С классом case еще сложнее сделать это правильно, если вы не сделаете его инвариантным. Я рекомендую сделать это и использовать дженерики и дисперсию в другом месте. Но мне нужно увидеть больше деталей, чтобы убедиться, что это сработает для вашего варианта использования.

person Rex Kerr    schedule 08.03.2012
comment
Спасибо за ваш ответ. Ваше решение, однако, не работает для меня в этом случае. Отбрасывание ковариации и создание инвариантности признака сработало бы, но это не то, что я хочу здесь. Разрешить методу (или, в моем случае, case-классу) принимать супертип, тоже неудовлетворительно. Мне любопытно, почему для кейс-классов все сложнее сделать правильно. Обратите внимание, что тот же код без ключевого слова case прекрасно работает. - person lapislazuli; 08.03.2012
comment
@lapislazuli - поскольку классы case включают сопутствующий метод, который их создает (принимая T в качестве аргумента), поэтому вы должны соблюдать ограничения метода, указанные выше. Если вы не включаете case, то класс не подразумевает метод в интерфейсе, который принимает Ts. - person Rex Kerr; 08.03.2012
comment
По какой причине в class F[+A] { def f(x: A) = ??? } этот x находится в contravariant position? Это связано с contravariance из T в Функция[-T1, +R? - person Kevin Meredith; 06.07.2016

Почти готово. Здесь:

scala> trait MyTrait[+T] {
     |   private case class MyClass[U >: T](c: U)
     | }
defined trait MyTrait

Это означает, что MyClass[Any] действителен для всех T. В этом корень того, почему нельзя использовать T в этой позиции, но для демонстрации этого требуется больше кода, чем мне сейчас нужно. :-)

person Daniel C. Sobral    schedule 08.03.2012