Вариант использования и примеры для шаблона типа с переменной типа - PullRequest
12 голосов
/ 06 сентября 2011

Я узнал, читая спецификацию, что scala поддерживает переменные типа привязки при сопоставлении с шаблоном типа:

Map(1 -> "one", 2 -> "two") match {
  case l: Map[k, v] =>
    // binds k to Int and v to String
    // k and v are types as shown here:
    val i: Iterator[Tuple2[k, v]] = l.iterator
    println(i.mkString(", "))
}

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

Мне пришло в голову, что Scala иногда нужны аннотации типов, такие как определение функции, поэтому я попытался:

def prepender(obj: Any) = obj match {
  case xs: List[a] => (x: a) => x :: xs
  case opt: Some[a] => (x: a) => x :: Nil
}

Но тогдатип возвращаемой функции странный:

prepender: (obj: Any)a with a => List[Any] forSome { type a; type a }

scala> val p = prepender(List(1,2))
p: a with a => List[Any] forSome { type a; type a } = <function1>

scala> p(1)
<console>:10: error: type mismatch;
 found   : Int(1)
 required: a(in value res7) with (some other)a(in value res7) where 
   type (some other)a(in value res7), type a(in value res7)

1 Ответ

13 голосов
/ 06 сентября 2011

Я надеюсь, что это не будет слишком длинным, но я серьезно сомневаюсь в этом, поэтому сначала я попытаюсь дать быстрый ответ: «Когда вы называете (абстрагируете) что-то, основной вариант использования ссылается на него потом". Ну, теперь это не помогло, не так ли?

Рассмотрим эту простую функцию 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[_].

...