scala - будь-яке проти підкреслення в дженериках


76

У чому полягає різниця між такими загальними визначеннями в Scala:

class Foo[T <: List[_]]

і

class Bar[T <: List[Any]]

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

Дякую!

Редагувати:

Чи можу я кинути ще одну в суміш?

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

7
Обмеження типу <: Anyніколи не змінює нічого. Кожен тип у Scala є <: Any.
Randall Schulz

Відповіді:


102

Добре, я вважав, що я повинен взяти це на себе, а не просто публікувати коментарі. На жаль, це буде довго, якщо ви хочете 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() } // 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є інваріантним, тому a 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]]

1
Ні, class Foo[T <: List[_]]це скорочення для class Foo[T <: List[Z] forSome { type Z }]. Точно так само List[Greets[_]]є скороченням List[Greets[Z] forSome { type Z }](і ні List[Greets[Z]] forSome { type Z }).
Джеспер Норденберг

Дох, безглуздий я! Дякую, я це виправив. Досить смішно, але моїм першим було перевірити статтю Девіда Р Маківера ( drmaciver.com/2008/03/existential-types-in-scala ), де саме йдеться про стенографію екзистенціалів та попереджається про їх неінтуїтивне знежирення. Річ у тім, що він, здається, сам це неправильно сприймає. Власне, трапилось так, що спосіб знебарвлення змінився незабаром після його статті (у Scala 2.7.1, див. Журнал змін на scala-lang.org/node/43#2.8.0 ). Я думаю, ця зміна сприяє плутанині.
Régis Jean-Gilles

Ну, легко змішати значення синтаксису екзистенціального типу. Принаймні поточна десугаризація є найбільш логічним ІМХО.
Jesper Nordenberg

куди ]входить останній class Foo[T <: List[Z forSome { type Z }]?
Джоел

Так, справді, ]в кінці зникли зниклі . Це мало бути class Foo[T <: List[Z forSome { type Z }]]. Виправлено зараз, дякую.
Régis Jean-Gilles

7

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

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

Ця форма говорить про те, що тип елемента Listневідомий, class Fooа не для вашої другої форми, що конкретно говорить про те List, що тип елемента Any.

Ознайомтеся з цією короткою пояснювальною статтею в блозі про екзистенційні типи в Scala ( EDIT : це посилання вже мертве, знімок доступний на archive.org )


3
Поки ви пояснюєте, що таке екзистенція, я думаю, що питання полягає не в тому, що таке екзистенціали загалом, а чи існує якась фактична помітна різниця між T[_](що є особливим випадком екзистенціального використання) та T[Any]?
Régis Jean-Gilles

Звичайно, є. У блозі, про який я згадував, є його гарна ілюстрація.
Randall Schulz

3
Я б хотів, щоб у вашій відповіді згадувалося, як коваріація є важливою для різниці між T[_]і T[Any], оскільки вона лежить в основі питання "навіщо навіть використовувати T[_] над T[Any]". Крім того, у своєму питанні Шон Конноллі прямо зазначив List[_]. Враховуючи, що Listнасправді є коваріантним, можна задатися питанням, чи справді в цьому випадку існує різниця між List[_]і List[Any].
Régis Jean-Gilles

2
Якщо T коваріантний і його типовим параметром є Anyверхня межа, тоді, здається, немає різниці між T[_]і T[Any]. Однак якщо у вас є T[+A <: B]компілятор, як не дивно, не можна зробити висновок T[_ <: B]з T[_].
Джеспер Норденберг

@ Джеспер Норденберг: Насправді в Scala до 2.8 це виведення працювало, воно "зламалося" в Scala 2.8. Насправді це було навмисно, оскільки, мабуть, це погано зіграло з рештою системи шрифтів і було суперечливим (хоча я не можу згадати подробиці).
Régis Jean-Gilles
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.