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

scala type-erasure

71152 просмотра

11 ответа

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

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

Автор: Daniel C. Sobral Источник Размещён: 12.09.2019 02:38

Ответы (11)


241 плюса

Решение

В этом ответе используется Manifest-API, который устарел в Scala 2.10. Пожалуйста, смотрите ответы ниже для более актуальных решений.

Scala был определен с помощью типа Erasure, поскольку виртуальная машина 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

При хранении элемента мы также храним его «Манифест». Манифест - это класс, экземпляры которого представляют типы Scala. Эти объекты содержат больше информации, чем JVM, что позволяет нам проверять полный параметризованный тип.

Обратите внимание, однако, что это Manifestвсе еще развивающаяся особенность. Как пример его ограничений, в настоящее время он ничего не знает о дисперсии и предполагает, что все является ко-вариантом. Я ожидаю, что она станет более стабильной и надежной, когда библиотека отражений Scala, находящаяся в стадии разработки, будет готова.

Автор: Daniel C. Sobral Размещён: 07.07.2009 07:14

93 плюса

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

К сожалению, это немного многословно, и вам нужна аннотация @unchecked для подавления предупреждения компилятора. TypeTag может быть включен в сопоставление с образцом автоматически компилятором в будущем: https://issues.scala-lang.org/browse/SI-6517

Автор: tksfz Размещён: 08.02.2014 01:19

63 плюса

Вы можете использовать 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Операция будет столь же точным WRT стирания , насколько это возможно , учитывая в области видимости Typeableэкземпляры доступны.

Автор: Miles Sabin Размещён: 11.01.2012 03:30

15 плюса

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

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

Автор: thricejamie Размещён: 14.06.2011 09:29

14 плюса

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

Автор: Alex Размещён: 13.03.2011 02:24

11 плюса

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

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

Автор: Jus12 Размещён: 14.12.2010 07:12

6 плюса

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

Автор: rained_in Размещён: 30.08.2012 02:29

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

Разве это не даже лучше, или я здесь упускаю смысл?

Автор: agilesteel Размещён: 12.07.2011 09:36

1 плюс

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

Автор: matanster Размещён: 06.12.2014 06:23

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))
Автор: Steve Robinson Размещён: 11.04.2018 08:50

-18 плюса

Использование паттерна

    list match  {
        case x:List if x.isInstanceOf(List[String]) => do sth
        case x:List if x.isInstanceOf(List[Int]) => do sth else
     }
Автор: Huangmao Quan Размещён: 03.07.2015 04:00
Вопросы из категории :
32x32