Как мне обойти стирание типа на Scala? Или, почему я не могу получить параметр типа моих коллекций?

352

Это печальный факт жизни в 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?!

Почему это и как мне обойти это?

  • 0
    Scala 2.8 Beta 1 RC4 только что внесла некоторые изменения в работу стирания типов. Я не уверен, если это напрямую влияет на ваш вопрос.
  • 1
    Вот только какие типы стирания для, что изменилось. Суть его можно суммировать как « Предложение: стирание« Объекта с А »- это« А »вместо« Объекта ». ) Фактическая спецификация довольно сложна. Во всяком случае, речь идет о миксинах, и этот вопрос касается дженериков.
Показать ещё 5 комментариев
Теги:
type-erasure

11 ответов

249
Лучший ответ

В этом ответе используется 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, которая в настоящее время находится в разработке, завершается.

  • 0
    Я согласен, что отвечать на вопросы такого рода - хорошая идея: я читал об этом где-то раньше, но найти его при переполнении стека гораздо проще .
  • 3
    Метод get может быть определен как for ((om, v) <- _map get key if om <:< m) yield v.asInstanceOf[T] .
Показать ещё 9 комментариев
87

Вы можете сделать это с помощью 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

  • 1
    Как насчет удаления ненужного [List String @unchecked] поскольку оно ничего не добавляет к этому сопоставлению с образцом (просто используйте case strlist if typeOf[A] =:= typeOf[String] => сделает это, или даже case _ if typeOf[A] =:= typeOf[String] => если связанная переменная не нужна в теле case ).
  • 1
    Я думаю, что это подойдет для данного примера, но я думаю, что большинство реальных применений выиграют от наличия типа элементов.
Показать ещё 6 комментариев
58

Вы можете использовать класс типа 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.

  • 13
    Следует отметить, что операция «cast» будет рекурсивно проходить через всю коллекцию и ее подколлекции и проверять, все ли участвующие значения имеют правильный тип. ( l1.cast[List[String]] выполняется примерно for (x<-l1) assert(x.isInstanceOf[String] ). Для больших структур данных или если преобразования происходят очень часто, это может быть недопустимым расходом.
14

Я придумал относительно простое решение, которое было бы достаточным в ситуациях ограниченного использования, по существу, обертывание параметризованных типов, которые пострадали бы от проблемы стирания типа в классах-оболочках, которые могут использоваться в утверждении соответствия.

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

14

Есть способ преодолеть проблему стирания типа в Scala. В методе "Преодоление типа стирания" в сопоставлении 1 и " Преодоление типа стирания" в сопоставлении 2 (разброс) есть некоторое объяснение того, как закодировать некоторые помощники для обертывания типов, включая Variance, для сопоставления.

  • 3
    Записи в блоге Джесси Эйхара очень стоит прочитать.
  • 0
    Это не преодолевает стирание типа. В его примере выполняется val x: Any = List (1,2,3); x match {case IntList (l) => println (s "Match $ {l (1)}"); case _ => println (s "Нет совпадений")} выдает "Нет совпадений"
Показать ещё 1 комментарий
9

Я нашел несколько лучшее обходное решение для этого ограничения в противном случае удивительного языка.

В 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).

  • 9
    «Ограничение в остальном замечательным языком» - это не ограничение Scala, а ограничение JVM. Возможно, Scala мог бы быть спроектирован так, чтобы включать информацию о типе, поскольку он работал на JVM, но я не думаю, что подобный дизайн сохранил бы совместимость с Java (т. Е., Как было задумано, вы можете вызывать Scala из Java).
  • 1
    В качестве продолжения поддержка постоянных обобщений для Scala в .NET / CLR является постоянной возможностью.
6

Так как Java не знает фактического типа элемента, мне было очень полезно просто использовать List[_]. Затем предупреждение исчезает, и код описывает реальность - это список чего-то неизвестного.

4

Мне интересно, подходит ли это обходному пути:

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")
     | }

Разве это не лучше, или я пропущу здесь пункт?

  • 3
    Не работает со списком (1, "a", "b"), который имеет тип List [Any]
  • 1
    Хотя точка зрения Салливана верна и есть проблемы с наследованием, я все же нашел это полезным.
1

Не решение, а способ жить с ним, не подметая его под ковер в целом: Добавление аннотации @unchecked. См. Здесь http://www.scala-lang.org/api/current/index.html#scala.unchecked

0

Я хотел добавить ответ, который обобщает проблему: Как получить представление 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))
-16

Использование защиты соответствия шаблону

    list match  {
        case x:List if x.isInstanceOf(List[String]) => do sth
        case x:List if x.isInstanceOf(List[Int]) => do sth else
     }
  • 5
    Нет, это не сработает.
  • 3
    Причина, по которой это не сработает, заключается в том, что isInstanceOf выполняет проверку во время выполнения на основе информации о типе, доступной для JVM. И эта информация во время выполнения не будет содержать аргумент типа для List (из-за стирания типа).

Ещё вопросы

Сообщество Overcoder
Наверх
Меню