Подтвердить что ты не робот

Scala - любой символ подчеркивания в дженериках

В чем разница между следующими определениями Generics в Scala:

class Foo[T <: List[_]]

и

class Bar[T <: List[Any]]

Моя кишка говорит мне, что они примерно одинаковы, но последнее более очевидно. Я нахожу случаи, когда первый компилирует, но последний не делает, но не может направить мой штрих на точную разницу.

Спасибо!

Edit:

Можно ли добавить другую в микс?

class Baz[T <: List[_ <: Any]]
4b9b3361

Ответ 1

Ладно, я решил, что мне нужно принять это вместо того, чтобы просто публиковать комментарии. Извините, это будет долго, если вы хотите, чтобы TL; DR пропустил до конца.

Как сказал Рэндалл Шульц, здесь _ сокращение от экзистенциального типа. А именно,

class Foo[T <: List[_]]

это сокращение для

class Foo[T <: List[Z] forSome { type Z }]

Обратите внимание, что вопреки тому, что упоминает ответ Рэндалла Шульца (полное раскрытие: я тоже ошибся в более ранней версии этого поста, спасибо Джесперу Норденбергу за указание на это), это не то же самое, что:

class Foo[T <: List[Z]] forSome { type Z }

и это не то же самое, что:

class Foo[T <: List[Z forSome { type Z }]]

Осторожно, легко ошибиться (как показывает мой предыдущий обман): автор статьи, на которую ссылается ответ Рэндалла Шульца, сам ошибся (см. Комментарии) и исправил ее позже. Моя главная проблема в этой статье состоит в том, что в показанном примере использование экзистенциалов должно избавить нас от проблемы типирования, но это не так. Проверьте код и попробуйте скомпилировать compileAndRun(helloWorldVM("Test")) или compileAndRun(intVM(42)). Да, не компилируется. Простое создание compileAndRun generic в A приведет к компиляции кода, и это будет намного проще. Короче говоря, это, вероятно, не лучшая статья, чтобы узнать об экзистенциалах и для чего они хороши (сам автор признается в комментарии, что статья "нуждается в уборке").

Поэтому я бы порекомендовал прочитать эту статью: http://www.artima.com/scalazine/articles/scalas_type_system.html, в частности, разделы "Экзистенциальные типы" и "Дисперсия в Java и Scala".

Важный момент, который вы должны получить из этой статьи, заключается в том, что экзистенциалы полезны (помимо возможности иметь дело с общими классами Java) при работе с нековариантными типами. Вот пример.

case class Greets[T]( private val name: T ) {
  def hello() { println("Hello " + name) }
  def getName: T = name
}

Этот класс является универсальным (обратите внимание, что он инвариантен), но мы можем видеть, что hello действительно не использует параметр типа (в отличие от getName), поэтому, если я получаю экземпляр Greets я всегда должен вызывать это, независимо от того, что T Если я хочу определить метод, который принимает экземпляр Greets и просто вызывает его метод hello, я мог бы попробовать это:

def sayHi1( g: Greets[T] ) { g.hello() } // Does not compile

Конечно же, это не компилируется, так как T появляется из ниоткуда.

Хорошо, тогда давайте сделаем метод общим:

def sayHi2[T]( g: Greets[T] ) { g.hello() }
sayHi2( Greets("John"))
sayHi2( Greets('Jack))

Отлично, это работает. Мы могли бы также использовать экзистенциалы здесь:

def sayHi3( g: Greets[_] ) { g.hello() }
sayHi3( Greets("John"))
sayHi3( Greets('Jack))

Тоже работает Таким образом, в целом, здесь нет реальной выгоды от использования экзистенциального (как в sayHi3) параметра типа (как в sayHi2).

Однако это изменяется, если Greets появляется как параметр типа для другого универсального класса. Например, мы хотим сохранить несколько экземпляров Greets (с разными T) в списке. Давай попробуем это:

val greets1: Greets[String] = Greets("John")
val greets2: Greets[Symbol] = Greets('Jack)
val greetsList1: List[Greets[Any]] = List( greets1, greets2 ) // Does not compile

Последняя строка не компилируется, потому что Greets является инвариантом, поэтому Greets[String] и Greets[Symbol] не могут рассматриваться как Greets[Any] хотя String и Symbol оба расширяют Any.

Хорошо, давайте попробуем с экзистенцией, используя сокращенную запись _:

val greetsList2: List[Greets[_]] = List( greets1, greets2 ) // Compiles fine, yeah

Это прекрасно компилируется, и вы можете сделать, как и ожидалось:

greetsSet foreach (_.hello)

Теперь, помните, что причина, по которой у нас возникла проблема проверки типов, заключалась в том, что Greets инвариантен. Если бы он был превращен в ковариантный класс (class Greets[+T]), то все бы работало "из коробки", и нам никогда бы не понадобились экзистенциальные компоненты.


Итак, чтобы подвести итог, экзистенциалы полезны для работы с универсальными инвариантными классами, но если универсальный класс не должен появляться сам как параметр типа для другого универсального класса, есть вероятность, что вам не нужны экзистенциалы и простое добавление параметра типа к твоему методу будет работать

Теперь вернитесь (наконец, я знаю!) К вашему конкретному вопросу, касающемуся

class Foo[T <: List[_]]

Поскольку List является ковариантным, это для всех намерений и целей такое же, как просто сказать:

class Foo[T <: List[Any]]

Так что в этом случае использование любого обозначения на самом деле просто вопрос стиля.

Однако, если вы замените List на Set, все изменится:

class Foo[T <: Set[_]]

Set инвариантен, и поэтому мы находимся в той же ситуации, что и с классом Greets из моего примера. Таким образом, вышесказанное действительно очень отличается от

class Foo[T <: Set[Any]]

Ответ 2

Первый является сокращением для экзистенциального типа, когда коду не нужно знать, что это за тип, или ограничивать его:

class Foo[T <: List[Z forSome { type Z }]]

Эта форма говорит о том, что тип элемента List неизвестен class Foo а не вашей второй форме, в которой конкретно говорится, что тип элемента List - Any.

Ознакомьтесь с этой краткой пояснительной статьей в блоге об экзистенциальных типах в Scala (РЕДАКТИРОВАТЬ: эта ссылка устарела, снимок доступен на archive.org)