2013-03-03 3 views
49

Каковы различия между следующими определениями генерик в Scala:Скала - Любой против подчеркивания в дженерик

class Foo[T <: List[_]] 

и

class Bar[T <: List[Any]] 

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

Спасибо!

Edit:

Могу ли я бросить другого в смесь?

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

Ограничение типа '': Any' никогда ничего не меняет. Каждый тип Scala - '<: Any'. –

ответ

64

ОК, я подумал, что мне нужно взять его на себя, а не просто размещать комментарии. Извините, это будет долго, если вы хотите, чтобы TLDR пропустил до конца.

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

class Foo[T <: List[_]] 

является обобщающим для

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

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

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 I всегда должен иметь возможность назвать это, независимо от того, 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 ковариантен, это во всех отношениях и purp ose то же самое, что и просто:

class Foo[T <: List[Any]] 

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

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

class Foo[T <: Set[_]] 

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

class Foo[T <: Set[Any]] 
+0

Нет, '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}'). –

+0

Дох, глупый! Спасибо, я исправил это. Забавно, но я первый, но должен был проверить статью Дэвида Р. Макивера (http://www.drmaciver.com/2008/03/existential-types-in-scala/), которая точно говорит об экзистенциальной стенографии и предупреждает о их неинтуитивный desugaring. Дело в том, что он, похоже, ошибается сам. Фактически, произошло то, что способ удаления десуранга изменился вскоре после его статьи (в scala 2.7.1 см. Журнал изменений на http://www.scala-lang.org/node/43#2.8.0). Я предполагаю, что это изменение связано с путаницей. –

+0

Ну, легко смешать смысл синтаксиса экзистенциального типа. По крайней мере, нынешний десураринг является самым логичным ИМХО. –

6

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

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

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

Ознакомьтесь с этим кратким пояснением blog article по экзистенциальным типам в Scala.

+3

Пока вы объясняете, что такое экзистенциальное, я думаю, что вопрос не в том, что существуют экзистенциальные существа вообще, но есть ли какая-либо реальная наблюдаемая разница между 'T [_]' (что является частным случаем экзистенциального использования) и 'T [Any] '? –

+0

Конечно, есть. В блоге, о котором я говорил, есть хорошая иллюстрация. –

+3

Я бы предпочел ** ваш ** ответ упомянуть, как ковариация имеет важное значение для разницы между 'T [_]' и 'T [Any]', поскольку в основе вопроса лежит «почему даже использовать« T [_] 'над' T [Any] '". Кроме того, в своем вопросе Шон Коннолли явно упомянул «Список [_]». Учитывая, что 'List' на самом деле является ковариантным, можно задаться вопросом, существует ли ** в этом случае **, действительно существует разница между« List [_] »и« List [Any] ». –

Смежные вопросы