Когда @uncheckedVariance требуется в Scala и почему он используется в GenericTraversableTemplate?
@uncheckedVariance
может использоваться для преодоления разрыва между аннотациями на сайте объявлений Scala и инвариантными обобщениями Java.
scala> import java.util.Comparator
import java.util.Comparator
scala> trait Foo[T] extends Comparator[T]
defined trait Foo
scala> trait Foo[-T] extends Comparator[T]
<console>:5: error: contravariant type T occurs in invariant position in type [-T]java.lang.Object with java.util.Comparator[T] of trait Foo
trait Foo[-T] extends Comparator[T]
^
scala> import annotation.unchecked._
import annotation.unchecked._
scala> trait Foo[-T] extends Comparator[T @uncheckedVariance]
defined trait Foo
Это говорит о том, что java.util.Comparator, естественно, противоположен, то есть параметр типа T
появляется в параметрах и никогда в возвращаемом типе.
Возникает вопрос: почему он также используется в библиотеке коллекций Scala, которая не выходит за пределы интерфейсов Java?
trait GenericTraversableTemplate[+A, +CC[X] <: Traversable[X]] extends HasNewBuilder[A, CC[A] @uncheckedVariance]
Каковы действительные варианты использования этой аннотации?
4 ответа
Проблема в том, что GenericTraversableTemplate используется дважды: один раз для изменяемых коллекций (где его параметр типа должен быть инвариантным), и один раз для неизменяемых коллекций (где ковариация всегда является королем).
Проверки типов GenericTraversableTemplate предполагают либо ковариацию, либо инвариантность для параметра типа A. Однако, когда мы наследуем это в изменчивой черте, мы должны выбрать инвариантность. И наоборот, мы хотели бы ковариации в неизменном подклассе.
Поскольку мы не можем абстрагироваться от аннотации дисперсии (пока;-)) в GenericTraversableTemplate, так что мы могли бы создать ее экземпляр в зависимости от подкласса, мы должны прибегнуть к приведению типа (@uncheckVariance по сути является приведением типа), Для дальнейшего чтения, я рекомендую свою диссертацию (извините;-)) или нашу недавнюю статью о битроте
В моей диссертации я описываю исчисление, Scalina, которое имеет границы и аннотации отклонений как часть доброго языка (более ранняя версия также доступна в виде рабочего документа). Актуальность этой дискуссии - следующий шаг, который я хочу предпринять при разработке этого исчисления: построить еще один слой поверх него, чтобы вы могли абстрагироваться от границ (легко) и аннотаций отклонений (заставляет мою голову крутиться). На самом деле, вы не просто добавили бы 1 дополнительный слой, а просто обобщили бы свои конструкции полиморфизма, чтобы они работали на всех уровнях, и превратили ваши "атрибуты" (границы, аннотации отклонений, требуемые неявные аргументы,...) в обычные типы со специальными видами, которые все подлежат абстракции.
Идея "атрибуты являются типами" хорошо объясняется Эдско де Врисом в контексте типов уникальности.
Уникальность печатания Упрощенная, Эдско де Фриз, Ринус Плазмейер и Дэвид Абрахамсон. В Olaf Chitil, Zoltán Horváth и Viktória Zsók (Eds.): IFL 2007, LNCS 5083, pp. 201-218, 2008.
Аннотация: Мы представляем систему типов уникальности, которая проще, чем как система уникальности Clean, так и система, которую мы предложили ранее. Новая система типов проста в реализации и дополнении к существующим компиляторам и может быть легко расширена с помощью расширенных функций, таких как типы более высокого ранга и непредсказуемость. Мы описываем нашу реализацию в Morrow, экспериментальном функциональном языке с обеими этими функциями. Наконец, мы доказываем надежность системы базовых типов в отношении лямбда-исчисления по требованию.
Я нашел другой случай, когда используется @uncheckedVariance - синтетический метод, который возвращает значение по умолчанию для параметра абстрактного типа:
M:\>scala -Xprint:typer -e "class C { def p[T >: Null](t: T = null) = t }"
[[syntax trees at end of typer]]// Scala source: (virtual file)
package <empty> {
final object Main extends java.lang.Object with ScalaObject {
def this(): object Main = {
Main.super.this();
()
};
def main(argv: Array[String]): Unit = {
val args: Array[String] = argv;
{
final class $anon extends scala.AnyRef {
def this(): anonymous class $anon = {
$anon.super.this();
()
};
class C extends java.lang.Object with ScalaObject {
<synthetic> def p$default$1[T >: Null <: Any]: Null @scala.annotation.unchecked.uncheckedVariance = null;
def this(): this.C = {
C.super.this();
()
};
def p[T >: Null <: Any](t: T = null): T = t
}
};
{
new $anon();
()
}
}
}
}
В Iterable.scala можно прочитать для +C тип того, что хвост возвращает это: « Мы требуем, чтобы для всех дочерних классов Iterable дисперсия дочернего класса и дисперсия параметра C, переданного в IterableOps, были одинаковыми. Мы не можем выразить это, так как нам не хватает полиморфизма дисперсии, поэтому в некоторых местах приходится прибегать к написанию C[A @uncheckedVariance] » .
Теперь, что касается допустимого использования этой аннотации, давайте рассмотрим следующий код:
class X[+T] {
var ref_ : Any = null
def ref:T = ref_.asInstanceOf[T]
def ref_=(ref: T@uncheckedVariance): Unit = ref_ = ref
}
Без @uncheckedVariance он не скомпилируется, потому что ковариантный T появляется в контравариантной позиции. Теперь, если Y[+T] расширяет X[+T] , а B расширяет A , тогда Y[B] расширяет X[A] , и вы можете написать:
val y: Y[B] = null
val x : X[A] = y
Это означает, что вы не можете написать:
y.ref = new A{}
Но вы можете написать, несмотря на то, что x равно y:
x.ref = new A{}
Это означает, что когда вы определяете Y[B] , вы, скорее всего, не собираетесь передавать его для ссылки на какой -то A , которому не хватает специфики B , и если какой-то A все же проберется, у вас будет неприятная ошибка. Вот почему дисперсия проверяется по умолчанию. Пример кода, вызывающего ClassCastException:
val y = new Y[B]
val x : X[A] = y
x.ref = new A
y.ref.b() // b() in B but not in A
Однако иногда ты знаешь, что делаешь, и абсолютно уверен, что ничего подобного произойти не может. Или, может быть, ваша документация явно предупреждает потенциального пользователя, @uncheckedVariance уже является четким предупреждением. По моему опыту, есть четыре обстоятельства, при которых вы можете использовать эту аннотацию.
- Когда вы работаете в частной или защищенной области, у вас больше контроля над тем, что и как используется. Совершенно верно.
- Когда вы расширяете код, предназначенный для использования таким образом, или реализуете шаблон, который явно требует этого. Тоже вполне годный.
- Когда вам это отчаянно нужно, потому что на вашем пути стоит ковариация, вы можете использовать setAcessible(true) точно так же, чтобы обойти ограничение области действия. Недействительно, но может помочь, особенно при попытке разобраться во всем и в качестве временного облегчения, когда код компилируется в процессе сложного рефакторинга.
- Когда это делает ваш API значительно более удобным, с преимуществом ковариации при незначительном риске неправильного использования. Дженерики не менее эффективны, чем их эквиваленты в Java, и не обязательно должны быть абсолютно безопасными. Например, если ваши классы в основном читаются и ковариация великолепна, но иногда в них записываются, а ковариация доставляет неудобства, для написания функций допустимо требовать дополнительной проверки.