Разница в совпадении шаблонов scala между массивом и списком

У меня есть следующие утверждения.

val a: Any = Array("1", "2", "3")
a match {
  case p: Array[Int] => println("int")
  case l: Array[String] => println("string")
}

val b: Any = List(1, 2, 3)
b match {
  case l: List[String] => println("string")
  case p: List[Int] => println("int")
}

Первый блок о Array компилируется без предупреждений и выводит «строку», а второй о List компилируется с предупреждениями, связанными с стиранием типа, и также выводит «строку».

Я кое-что знаю о стирании типов в JVM. Во время выполнения JVM не может точно знать общий тип контейнера (например, List). Но почему Array может избежать стирания типа во время выполнения и подобрать правильный тип?

Я попытался найти ответ в исходном коде scala. Единственное, что я обнаружил, это то, что Array использует ClassTag, а List - нет.

Хотелось бы узнать, как работает ClassTag. ClassTag - это обходной путь стирания типа? И почему контейнеры, подобные List, не были реализованы с ClassTag, чтобы избежать стирания типа.


person aihex    schedule 22.09.2015    source источник


Ответы (2)


Scala работает на JVM и наследует ее ограничения. Java использует стирание типов, поэтому все параметризованные типы одинаковы во время выполнения. Информация о типе с них стирается. Это было сделано для обеспечения совместимости со старыми версиями Java, которые вообще не могли использовать параметры типа.

Но массивы - это особый случай в Java, они хранят информацию о типе. Так делают массивы scala. Это было необходимо для сохранения эффективных неупакованных значений памяти внутри массивов.

Вы должны просто предположить, что вся информация о типах теряется во время выполнения. Поэтому используйте несколько тегов для сопоставления с ними.


ClassTags не связаны с переносом массива. Информация обо всех типах предоставляется самой JVM.

В Java существует особая практика использования AnyRef и динамического преобразования типов каждый раз, когда возникают трудности с выражением отношений типов. Scala обеспечивает более выразительные возможности для статического описания типов без преобразований во время выполнения. А стиль кодирования Scala поощряет использование тяжелых конструкций типов для обеспечения безопасности типов кода.

ClassTags и TypeTags - это инструменты, которые можно использовать только со статически типизированным кодом. Они содержат информацию о классе и типе, полученную компилятором во время компиляции. Если бы он мог получать типы статически, он мог бы предоставить вам теги типов для доступа к этим типам.

Это полезно, когда вы пишете какую-то библиотеку и не знаете, как ее использовать. Таким образом, вам требуется ClassTag в качестве неявного параметра, и он будет заполнен компилятором с соответствующим типом на основе другого аргумента, предоставленного для вызова функции. Неявные параметры помещаются как требование кодом библиотеки и автоматически заполняются внешним кодом, вызывающим библиотеку.

person ayvango    schedule 22.09.2015
comment
Отличный ответ! Итак, идея состоит в том, чтобы поддерживать совместимость с массивами java, массивы scala вводят ClassTag для этого? - person aihex; 23.09.2015
comment
кратко объяснил ClassTags - person ayvango; 23.09.2015

В этих случаях вы можете рассмотреть возможность использования класса Typeable type, который вы получаете с shapeless для безопасного преобразования типов. Например.:

scala> import shapeless.syntax.typeable._
import shapeless.syntax.typeable._

scala> val b: Any = List(1, 2, 3)
b: Any = List(1, 2, 3)

scala> b.cast[List[String]]
res1: Option[List[String]] = None

scala> b.cast[List[Int]]
res2: Option[List[Int]] = Some(List(1, 2, 3))

Как вы можете заметить, метод cast[T], добавленный к каждому типу с помощью shapeless через implicits, возвращает Option[T], значение которого равно None в случае неудачного преобразования, Some в случае успеха.

Если хотите, можете посмотреть исходный код Возможность ввода. Тем не менее, я предлагаю вам перед этим выпить чашечку хорошего кофе. :)

person lambdista    schedule 23.09.2015