Как мне обойти стирание типа на Scala? Или, почему я не могу получить параметр типа моих коллекций?
Это печальный факт жизни на Scala, что если вы создаете экземпляр List[Int], вы можете проверить, что ваш экземпляр является списком, и вы можете проверить, что любой отдельный элемент его является Int, но не то, что это список[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 был определен с стиранием типа, потому что виртуальная машина Java (JVM), в отличие от Java, не получала универсальные шаблоны. Это означает, что, во время выполнения, существует только класс, а не его параметры типа. В Примере JVM знает, что он обрабатывает a
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
при хранении элемента мы храним a "Манифест" этого тоже. Манифест-это класс, экземпляры которого представляют типы Scala. Эти объекты имеют больше информации, чем JVM, что позволяет нам тестировать для полного, параметризованного типа.
обратите внимание, однако, что a
Manifest
по-прежнему является развивающейся функцией. В качестве примера своих ограничений он в настоящее время ничего не знает о дисперсии и предполагает, что все является ко-вариантом. Я ожидаю, что он станет более стабильным и прочным, как только библиотека Scala reflection, в настоящее время развитие, получает законченным.
вы можете сделать это с помощью 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!") }
вы также можете сделать это с помощью Classstags (что избавляет вас от необходимости зависеть от 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!") }
Classstags можно использовать до тех пор, пока вы не ожидаете параметр типа
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))
The
cast
операция будет максимально точным стиранием wrt, учитывая область действияTypeable
экземпляры.
Я придумал относительно простое решение, которое было бы достаточно в ситуациях ограниченного использования, по существу обертывая параметризованные типы, которые будут страдать от проблемы стирания типов в классах-оболочках, которые могут использоваться в операторе match.
case class StringListHolder(list:List[String]) StringListHolder(List("str1","str2")) match { case holder: StringListHolder => holder.list foreach println }
Это имеет ожидаемый результат и ограничивает содержимое нашего класса case желаемым типом, строковыми списками.
подробнее здесь: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#scala.unchecked
Я хотел добавить ответ, который обобщает проблему: как получить строковое представление типа моего списка во время выполнения
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))