Как обойти стирание стилей на Scala? Или, почему я не могу получить параметр типа моих коллекций?
Это печальный факт жизни в Scala, что если вы создаете экземпляр List [Int], вы можете проверить, что ваш экземпляр является List, и вы можете проверить, что любой его отдельный элемент является Int, но не тот это List [Int], что легко проверить:
scala> List(1,2,3) match {
| case l : List[String] => println("A list of strings?!")
| case _ => println("Ok")
| }
warning: there were unchecked warnings; re-run with -unchecked for details
A list of strings?!
Параметр -unchecked ставит вину прямо на стирание типа:
scala> List(1,2,3) match {
| case l : List[String] => println("A list of strings?!")
| case _ => println("Ok")
| }
<console>:6: warning: non variable type-argument String in type pattern is unchecked since it is eliminated by erasure
case l : List[String] => println("A list of strings?!")
^
A list of strings?!
Почему это и как мне обойти это?
Ответы
Ответ 1
В этом ответе используется Manifest
-API, который устарел на Scala 2.10. Подробнее см. Ответы ниже.
Scala был определен с помощью типа Erasure, потому что виртуальная машина Java (JVM), в отличие от Java, не получала дженерики. Это означает, что во время выполнения существует только класс, а не его параметры типа. В примере JVM знает, что он обрабатывает scala.collection.immutable.List
, но не этот список параметризуется с помощью Int
.
К счастью, в Scala есть функция, позволяющая обойти это. Его Манифест. Манифест - это класс, экземпляры которого представляют собой объекты, представляющие типы. Поскольку эти экземпляры являются объектами, вы можете передавать их, хранить их и обычно вызывать методы на них. При поддержке неявных параметров он становится очень мощным инструментом. Возьмем следующий пример, например:
object Registry {
import scala.reflect.Manifest
private var map= Map.empty[Any,(Manifest[_], Any)]
def register[T](name: Any, item: T)(implicit m: Manifest[T]) {
map = map.updated(name, m -> item)
}
def get[T](key:Any)(implicit m : Manifest[T]): Option[T] = {
map get key flatMap {
case (om, s) => if (om <:< m) Some(s.asInstanceOf[T]) else None
}
}
}
scala> Registry.register("a", List(1,2,3))
scala> Registry.get[List[Int]]("a")
res6: Option[List[Int]] = Some(List(1, 2, 3))
scala> Registry.get[List[String]]("a")
res7: Option[List[String]] = None
При хранении элемента мы также сохраняем "Манифест". Манифест - это класс, экземпляры которого представляют типы Scala. Эти объекты имеют больше информации, чем JVM, что позволяет нам тестировать полный, параметризованный тип.
Обратите внимание, однако, что a Manifest
все еще развивается. В качестве примера своих ограничений он в настоящее время ничего не знает об дисперсии и предполагает, что все является ко-вариантом. Я ожидаю, что он станет более стабильным и надежным после того, как библиотека отражений Scala, которая в настоящее время находится в разработке, завершается.
Ответ 2
Вы можете сделать это с помощью TypeTags (как уже упоминал Даниил, но я просто укажу его явно):
import scala.reflect.runtime.universe._
def matchList[A: TypeTag](list: List[A]) = list match {
case strlist: List[String @unchecked] if typeOf[A] =:= typeOf[String] => println("A list of strings!")
case intlist: List[Int @unchecked] if typeOf[A] =:= typeOf[Int] => println("A list of ints!")
}
Вы также можете сделать это с помощью ClassTags (что избавляет вас от необходимости зависеть от scala -reflect):
import scala.reflect.{ClassTag, classTag}
def matchList2[A : ClassTag](list: List[A]) = list match {
case strlist: List[String @unchecked] if classTag[A] == classTag[String] => println("A List of strings!")
case intlist: List[Int @unchecked] if classTag[A] == classTag[Int] => println("A list of ints!")
}
ClassTags можно использовать, если вы не ожидаете, что параметр типа A
сам будет общим типом.
К сожалению, это немного подробный, и вам нужно аннотацию @unchecked для подавления предупреждения компилятора. ТипTag может автоматически включаться в совпадение шаблонов в будущем: https://issues.scala-lang.org/browse/SI-6517
Ответ 3
Вы можете использовать класс типа Typeable
из shapeless, чтобы получить результат, который вам нужен,
Пример сеанса REPL,
scala> import shapeless.syntax.typeable._
import shapeless.syntax.typeable._
scala> val l1 : Any = List(1,2,3)
l1: Any = List(1, 2, 3)
scala> l1.cast[List[String]]
res0: Option[List[String]] = None
scala> l1.cast[List[Int]]
res1: Option[List[Int]] = Some(List(1, 2, 3))
Операция cast
будет как можно более точной портить стирание с учетом доступных экземпляров Typeable
.
Ответ 4
Я придумал относительно простое решение, которое было бы достаточным в ситуациях ограниченного использования, по существу, обертывание параметризованных типов, которые пострадали бы от проблемы стирания типа в классах-оболочках, которые могут использоваться в утверждении соответствия.
case class StringListHolder(list:List[String])
StringListHolder(List("str1","str2")) match {
case holder: StringListHolder => holder.list foreach println
}
Это имеет ожидаемый результат и ограничивает содержимое нашего класса case желаемым типом, String Lists.
Подробнее здесь: http://www.scalafied.com/?p=60
Ответ 5
В Scala есть способ преодолеть проблему стирания типов. В Преодоление стирания типов при сопоставлении 1 и Преодоление стирания типов при сопоставлении 2 (дисперсия) приводятся некоторые объяснения того, как кодировать некоторые помощники для переноса типов, включая дисперсию, для сопоставления.
Ответ 6
Я нашел несколько лучшее обходное решение для этого ограничения в противном случае удивительного языка.
В Scala проблема стирания типа не возникает с массивами. Я думаю, что это проще продемонстрировать на примере.
Скажем, у нас есть список (Int, String)
, тогда следующее дает предупреждение о стирании типа
x match {
case l:List[(Int, String)] =>
...
}
Чтобы обойти это, сначала создайте класс case:
case class IntString(i:Int, s:String)
то в сопоставлении с образцом сделайте что-то вроде:
x match {
case a:Array[IntString] =>
...
}
который, кажется, работает отлично.
Это потребует незначительных изменений в вашем коде для работы с массивами вместо списков, но не должно быть серьезной проблемой.
Обратите внимание, что использование case a:Array[(Int, String)]
все равно даст предупреждение стирания типа, поэтому необходимо использовать новый класс контейнера (в этом примере IntString
).
Ответ 7
Так как Java не знает фактического типа элемента, мне было очень полезно просто использовать List[_]
. Затем предупреждение исчезает, и код описывает реальность - это список чего-то неизвестного.
Ответ 8
Мне интересно, подходит ли это обходному пути:
scala> List(1,2,3) match {
| case List(_: String, _*) => println("A list of strings?!")
| case _ => println("Ok")
| }
Он не соответствует случаю "пустой список", но он дает ошибку компиляции, а не предупреждение!
error: type mismatch;
found: String
requirerd: Int
Это, с другой стороны, кажется, работает.
scala> List(1,2,3) match {
| case List(_: Int, _*) => println("A list of ints")
| case _ => println("Ok")
| }
Разве это не лучше, или я пропущу здесь пункт?
Ответ 9
Не решение, а способ жить с ним, не подметая его под ковер в целом:
Добавление аннотации @unchecked
. См. Здесь http://www.scala-lang.org/api/current/index.html#scala.unchecked
Ответ 10
Я хотел добавить ответ, который обобщает проблему: Как получить представление String типа моего списка во время выполнения
import scala.reflect.runtime.universe._
def whatListAmI[A : TypeTag](list : List[A]) = {
if (typeTag[A] == typeTag[java.lang.String]) // note that typeTag[String] does not match due to type alias being a different type
println("its a String")
else if (typeTag[A] == typeTag[Int])
println("its a Int")
s"A List of ${typeTag[A].tpe.toString}"
}
val listInt = List(1,2,3)
val listString = List("a", "b", "c")
println(whatListAmI(listInt))
println(whatListAmI(listString))
Ответ 11
Использование защиты соответствия шаблону
list match {
case x:List if x.isInstanceOf(List[String]) => do sth
case x:List if x.isInstanceOf(List[Int]) => do sth else
}