Добре, я вважав, що я повинен взяти це на себе, а не просто публікувати коментарі. На жаль, це буде довго, якщо ви хочете 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
загального в in 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
насправді не використовує параметр type (на відміну від getName
), тому, якщо я отримую екземпляр, Greets
я завжди можу мати можливість його викликати, T
є. Якщо я хочу визначити метод, який бере Greets
екземпляр і просто викликає його hello
метод, я можу спробувати це:
def sayHi1( g: Greets[T] ) { g.hello() }
Звичайно, це не компілюється, як 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 )
Останній рядок не компілюється, оскільки Greets
є інваріантним, тому a Greets[String]
і Greets[Symbol]
не може розглядатися як Greets[Any]
парний, хоча String
і той, Symbol
і інший поширюється Any
.
Добре, спробуємо з екзистенцією, використовуючи скорочений запис _
:
val greetsList2: List[Greets[_]] = List( greets1, greets2 )
Це добре компілюється, і ви можете зробити, як очікувалося:
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]]
<: Any
ніколи не змінює нічого. Кожен тип у Scala є<: Any
.