Я надеюсь, что это не будет слишком длинным, но я серьезно сомневаюсь в этом, поэтому сначала я попытаюсь дать быстрый ответ: «Когда вы называете (абстрагируете) что-то, основной вариант использования ссылается на него потом". Ну, теперь это не помогло, не так ли?
Рассмотрим эту простую функцию Scala:
val sum = (a: Int, b: Int) => a + b
Компилятору не нужно знать, что a
- это a
, а b
- это b
. Все, что нужно знать, это то, что a
и b
имеют тип Int
и что a
предшествует b
(что в данном случае не имеет значения, так как сложение коммутативно, но компилятор все равно заботится !). Scala предлагает (не поймите меня неправильно, мне это тоже нравится) синтаксис, дружественный к компилятору, который служит доказательством этой «гипотезы».
val sum: (Int, Int) => Int = _ + _ // where the 1st _ differs from the 2nd _
Теперь взгляните на это:
case x: SomeTypeParameterizedWith[AnotherType] // AnotherType is erased anyway
case x: SomeParameterizedType[_] // Existential type
case x: SomeParameterizedType[kind] // Existential type which you can reference
Если вас не интересует аргумент типа, используйте синтаксис заполнителя. Когда вы (по любой причине) заботитесь, вы должны называть аргумент типа строчными буквами, чтобы компилятор знал, что вы хотите трактовать его как идентификатор.
Вернуться к вашему вопросу.
Основное использование для экзистенциальных типов - работа с подстановочными типами Java.
Это взято из Программирование в Scala - экзистенциальные типы и было немного изменено вашим по-настоящему.
// This is a Java class with wildcards
public class Wild {
public java.util.Collection<?> contents() {
java.util.Collection<String> stuff = new Vector<String>();
stuff.add("a");
stuff.add("b");
stuff.add("see");
return stuff;
}
}
// This is the problem
import scala.collection.mutable.Set
val iter = (new Wild).contents.iterator
val set = Set.empty[???] // what type goes here?
while (iter.hasMore)
set += iter.next()
// This is the solution
def javaSet2ScalaSet[T](jset: java.util.Collection[T]): Set[T] = {
val sset = Set.empty[T] // now T can be named!
val iter = jset.iterator
while (iter.hasNext)
sset += iter.next()
sset
}
Хорошо, так что только что произошло? Простые дженерики, никакой магии нет ?! Если вы имеете дело с генериками на повседневной основе, это выглядит нормально для вас, но вы забываете, что сверхсверх-концепция введения аргументов типа в область действия работает только на классах и методах. Что, если вы находитесь вне класса или метода, просто в какой-то случайной области видимости посреди ничего (например, REPL)? Или что, если вы находитесь в классе или методе, но аргументы типа не были введены в их области? Здесь ваш вопрос и этот ответ вступают в игру.
val set = new Wild().contents match {
case jset: java.util.Collection[kind] => {
val sset = Set.empty[kind]
val iter = jset.iterator
while (iter.hasNext)
sset += iter.next()
sset
}
}
Идентификатор kind
необходим, чтобы компилятор мог убедиться, что вы ссылаетесь на то же самое.
Обратите внимание, что вы не можете просто добавить строки в set
, так как тип set
равен Set[_]
.