Как мне обойти стирание типа на Scala? Или, почему я не могу получить параметр типа моих коллекций?
Печальный факт жизни в Scala заключается в том, что если вы создаете экземпляр List[Int], вы можете убедиться, что ваш экземпляр является списком, и вы можете убедиться, что любой отдельный его элемент является 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?!
Почему это так и как мне обойти это?
11 ответов
Этот ответ использует
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, что позволяет нам проверять полный параметризованный тип.
Обратите внимание, однако, что Manifest
все еще развивается особенность. Как пример его ограничений, в настоящее время он ничего не знает о дисперсии и предполагает, что все является ко-вариантом. Я ожидаю, что она станет более стабильной и надежной, когда библиотека отражений Scala, находящаяся в стадии разработки, будет готова.
Вы можете сделать это, используя 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-рефлекса):
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 можно использовать до тех пор, пока вы не ожидаете параметр type A
сам по себе быть универсальным типом.
К сожалению, это немного многословно, и вам нужна аннотация @unchecked для подавления предупреждения компилятора. TypeTag может быть автоматически включен в сопоставление с образцом компилятором в будущем: https://issues.scala-lang.org/browse/SI-6517
Вы можете использовать Typeable
наберите класс из бесформенного, чтобы получить желаемый результат,
Образец сессии 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
экземпляры доступны.
Я придумал относительно простое решение, которое было бы достаточно в ситуациях ограниченного использования, по существу, для упаковки параметризованных типов, которые пострадали бы от проблемы стирания типов в классах-обертках, которые можно использовать в операторе сопоставления.
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
В Scala есть способ преодолеть проблему стирания типов. В книге "Преодоление стирания типов при сопоставлении 1" и " Преодоление стирания типов при сопоставлении 2" (дисперсия) приводятся некоторые объяснения того, как закодировать некоторые помощники для переноса типов, включая дисперсию, для сопоставления.
Я нашел немного лучший обходной путь для этого ограничения в остальном отличном языке.
В 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
).
Поскольку Java не знает фактический тип элемента, я нашел наиболее полезным просто использовать List[_]
, Затем предупреждение исчезает, и код описывает реальность - это список чего-то неизвестного.
Мне интересно, если это подходящий обходной путь:
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")
| }
Разве это не даже лучше, или я здесь упускаю смысл?
Не решение, а способ жить с этим, не сметая его полностью под ковер: добавление @unchecked
аннотаций. Смотрите здесь - http://www.scala-lang.org/api/current/index.html
Я хотел добавить ответ, который обобщает проблему: Как получить представление типа 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))
Использование паттерна
list match {
case x:List if x.isInstanceOf(List[String]) => do sth
case x:List if x.isInstanceOf(List[Int]) => do sth else
}