Як мені подолати стирання типу Scala? Або чому я не можу отримати параметр типу моїх колекцій?


370

Сумний факт життя Scala - це те, що якщо ви створюєте список [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?!

Опція, що не використовується, покладає звинувачення прямо на стирання типу:

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?!

Чому це так, і як я його обходжу?


Скала 2.8 Beta 1 RC4 лише вніс деякі зміни в спосіб стирання типу. Я не впевнений, чи це безпосередньо впливає на ваше питання.
Скотт Моррісон

1
Ось тільки які типи стирання до , що змінилося. Короткий зміст може бути зведений як " Пропозиція: стирання" Об'єкта з A "є" A "замість" Object ". " Фактична специфікація є досить складнішою. Йдеться про міксини, у будь-якому разі, і це питання стосується дженериків.
Даніель К. Собрал

Дякую за роз’яснення - я новачок зі скалою. Я відчуваю, що зараз поганий час стрибнути в Скалу. Раніше я міг дізнатися зміни в 2.8 з хорошої бази, пізніше мені ніколи не доведеться знати різницю!
Скотт Моррісон

1
Ось дещо пов’язане питання щодо TypeTags .
pvorb

2
Бігаючи scala 2.10.2, я побачив замість цього попередження: <console>:9: warning: fruitless type test: a value of type List[Int] cannot also be a List[String] (but still might match its erasure) case list: List[String] => println("a list of strings?") ^я вважаю, що ваше запитання та відповідь є дуже корисними, але я не впевнений, чи корисне це оновлене попередження для читачів.
Кевін Мередіт

Відповіді:


243

У цій відповіді використовується 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, яка зараз знаходиться в стадії розробки, закінчиться.


3
getМетод може бути визначений як for ((om, v) <- _map get key if om <:< m) yield v.asInstanceOf[T].
Аарон Новструп

4
@Aaron Дуже гарна пропозиція, але я боюся, що це може затьмарити код для людей, відносно нових для Scala. Я не мав особливого досвіду роботи зі Скалою, коли я писав той код, який був колись до того, як я поставив це питання / відповідь.
Даніель Ч. Собрал

6
@KimStebel Ви знаєте, що TypeTagнасправді автоматично використовується для відповідності шаблонів? Класно, так?
Даніель К. Собрал

1
Класно! Можливо, ви повинні додати це до відповіді.
Кім Стебель

1
Щоб відповісти на моє власне питання трохи вище: Так, компілятор генерує сам Manifestпарам, див.: Stackoverflow.com/a/11495793/694469 "екземпляр [manifest / type-tag] [...] створюється неявно компілятором "
KajMagnus

96

Це можна зробити за допомогою 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 (що позбавить вас від необхідності залежати від масштабування):

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сам по собі буде загальним типом.

На жаль, це трохи дослівно, і вам потрібно @uncked анотація, щоб придушити попередження компілятора. Компілятор TypeTag може бути включений у відповідність шаблонів автоматично в майбутньому компілятором: https://isissue.scala-lang.org/browse/SI-6517


2
Що з видаленням непотрібного, [List String @unchecked]оскільки воно нічого не додає до цього збігу шаблонів (просто використовуючи case strlist if typeOf[A] =:= typeOf[String] =>це зробимо, або навіть case _ if typeOf[A] =:= typeOf[String] =>якщо зв'язана змінна не потрібна в тілі поля case).
Надер Ганбарі

1
Я думаю, що це би спрацювало для даного прикладу, але я думаю, що більшість реальних звичаїв виграють від того, щоб мати тип елементів.
tksfz

У наведених вище прикладах, чи не відзначається неперевірена частина перед умовою охорони? Хіба ви не отримаєте виняток з класового лиття, переглядаючи відповідність першого об'єкта, який не може бути переданий рядку?
Toby

Хм ні, я вважаю, що перед застосуванням захисту не є жодного складу - неперевірений біт є своєрідним відключенням, поки не =>буде виконаний код праворуч від . (І коли виконується код на rhs, охоронці надають статичну гарантію на тип елементів. Там може бути акторський
склад

Чи створює це рішення значні витрати на експлуатацію?
stanislav.chetvertkov

65

Ви можете використовувати 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екземпляри доступні.


14
Слід зазначити, що операція "лиття" буде рекурсивно проходити через всю колекцію та її підколекції та перевіряти, чи мають усі задіяні значення потрібного типу. (Тобто, l1.cast[List[String]]приблизно for (x<-l1) assert(x.isInstanceOf[String]) Для великих структур даних або якщо трансляції трапляються дуже часто, це може бути неприйнятним накладними витратами.
Домінік Унрух

16

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

case class StringListHolder(list:List[String])

StringListHolder(List("str1","str2")) match {
    case holder: StringListHolder => holder.list foreach println
}

Це має очікуваний вихід і обмежує вміст нашого класу випадків потрібним типом, списками рядків.

Детальніше тут: http://www.scalafied.com/?p=60


14

Існує спосіб подолати проблему стирання типу Scala. У розділі " Подолання стирання типу" у зіставленні 1 та " Подолання стирання типу" у "Збігу 2" (Варіант) є деякі пояснення того, як кодувати деякі помічники для обгортання типів, включаючи Варіант, для відповідності.


Це не долає стирання типу. У його прикладі, роблячи val x: Any = Список (1,2,3); x match {case IntList (l) => println (s "Матч $ {l (1)}"); case _ => println (s "No match")} видає "No match"
user48956

Ви можете подивитися на макроси Scala 2.10.
Алекс

11

Я знайшов дещо кращий спосіб вирішити це обмеження інакше приголомшливої ​​мови.

У Scala питання стирання типу не виникає з масивами. Я думаю, що простіше продемонструвати це на прикладі.

Скажімо, у нас є список (Int, String), а далі наведено попередження про стирання типу

x match {
  case l:List[(Int, String)] => 
  ...
}

Щоб вирішити це, спочатку створіть клас корпусу:

case class IntString(i:Int, s:String)

то в узгодженні шаблону робіть щось на кшталт:

x match {
  case a:Array[IntString] => 
  ...
}

який, здається, працює чудово.

Це потребує незначних змін у вашому коді для роботи з масивами замість списків, але це не повинно бути головною проблемою.

Зауважте, що використання case a:Array[(Int, String)]все ще надасть попередження про стирання типу, тому необхідно використовувати новий клас контейнерів (у цьому прикладі IntString).


10
"обмеження інакше приголомшливої ​​мови" - це менше обмеження Scala і більше обмеження JVM. Можливо, Scala міг бути розроблений таким чином, щоб він включав інформацію про тип, коли він працював на JVM, але я не думаю, що така конструкція зберегла б сумісність з Java (тобто, як задумано, ви можете назвати Scala з Java.)
Карл G

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 [Будь-який]
sullivan-

1
Хоча точка суллівана є правильною і є проблеми, пов'язані з успадкуванням, я все-таки вважаю це корисним.
Сет


0

Я хотів додати відповідь, яка узагальнює проблему: Як отримати рядкове представлення типу мого списку під час виконання

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

-18

Використовуючи захист відповідності шаблону

    list match  {
        case x:List if x.isInstanceOf(List[String]) => do sth
        case x:List if x.isInstanceOf(List[Int]) => do sth else
     }

4
Причина, через яку ця не працює, полягає в тому, що isInstanceOfвона виконує перевірку на основі інформації про тип, доступну для JVM. І ця інформація про час виконання не буде містити аргумент типу List(через стирання типу).
Домінік Унрух
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.