Это печальный факт жизни в 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?!
Почему это и как мне обойти это?
В этом ответе используется
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, которая в настоящее время находится в разработке, завершается.
get
может быть определен как for ((om, v) <- _map get key if om <:< m) yield v.asInstanceOf[T]
.
Вы можете сделать это с помощью 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
[List String @unchecked]
поскольку оно ничего не добавляет к этому сопоставлению с образцом (просто используйте case strlist if typeOf[A] =:= typeOf[String] =>
сделает это, или даже case _ if typeOf[A] =:= typeOf[String] =>
если связанная переменная не нужна в теле case
).
Вы можете использовать класс типа 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
.
l1.cast[List[String]]
выполняется примерно for (x<-l1) assert(x.isInstanceOf[String]
). Для больших структур данных или если преобразования происходят очень часто, это может быть недопустимым расходом.
Я придумал относительно простое решение, которое было бы достаточным в ситуациях ограниченного использования, по существу, обертывание параметризованных типов, которые пострадали бы от проблемы стирания типа в классах-оболочках, которые могут использоваться в утверждении соответствия.
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 (разброс) есть некоторое объяснение того, как закодировать некоторые помощники для обертывания типов, включая Variance, для сопоставления.
Я нашел несколько лучшее обходное решение для этого ограничения в противном случае удивительного языка.
В 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#scala.unchecked
Я хотел добавить ответ, который обобщает проблему: Как получить представление 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
}
isInstanceOf
выполняет проверку во время выполнения на основе информации о типе, доступной для JVM. И эта информация во время выполнения не будет содержать аргумент типа для List
(из-за стирания типа).