Jak obejść Typ erasure na Scali? Albo dlaczego nie mogę uzyskać parametru typ moich kolekcji?

Smutnym faktem w Scali Jest to, że jeśli tworzysz instancję listy [Int], możesz zweryfikować, czy Twoja instancja jest listą, i możesz zweryfikować, że każdy jej pojedynczy element jest Int, ale nie, że jest to lista[Int], co można łatwo zweryfikować:

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

Opcja-unchecked stawia winę na typ erasure:

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?!
Dlaczego i jak to obejść?
Author: Daniel C. Sobral, 2009-07-07

11 answers

Ta odpowiedź używa Manifest - API, które jest przestarzałe od Scali 2.10. Więcej aktualnych rozwiązań znajdziesz w odpowiedziach poniżej.

Scala została zdefiniowana za pomocą typu Erasure, ponieważ wirtualna maszyna Javy (JVM), w przeciwieństwie do Javy, nie otrzymała generycznych. Oznacza to, że w czasie wykonywania istnieje tylko klasa, a nie jej parametry typu. W przykładzie, JVM wie, że obsługuje scala.collection.immutable.List, ale nie, że ta lista jest parametryzowana przez Int.

Na szczęście w Scali jest funkcja, która pozwoli Ci to obejść. To Manifest . Manifest to klasa, której instancje są obiektami reprezentującymi typy. Ponieważ te instancje są obiektami, można je przekazywać, przechowywać i ogólnie wywoływać na nich metody. Dzięki obsłudze ukrytych parametrów staje się bardzo potężnym narzędziem. Weźmy na przykład następujący przykład:

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

Gdy przechowujemy element, przechowujemy też "Manifest" tego elementu. Manifest jest klasą, której instancje reprezentują typy Scali. Te obiekty mają więcej informacji niż JVM, co pozwala nam przetestować pełny, sparametryzowany Typ.

Zauważ jednak, że Manifest jest wciąż ewoluującą cechą. Jako przykład swoich ograniczeń, obecnie nie wie nic o wariancji i zakłada, że wszystko jest współwariantem. Spodziewam się, że stanie się bardziej stabilny i solidny, gdy biblioteka Scala reflection, obecnie rozwijana, zostanie ukończona.

 242
Author: Daniel C. Sobral,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Możesz to zrobić za pomocą TypeTags (jak już wspomina Daniel, ale po prostu przeliteruję to wprost):

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

Możesz to również zrobić używając ClassTags (co oszczędza Ci konieczności polegania na 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 mogą być używane tak długo, jak długo nie oczekujesz, że parametr type A będzie typem generycznym.

Niestety jest to trochę gadatliwe i potrzebujesz adnotacji @ unchecked, aby stłumić Ostrzeżenie kompilatora. TypeTag może być włączony do wzorzec dopasowuje się automatycznie przez kompilator w przyszłości: https://issues.scala-lang.org/browse/SI-6517

 101
Author: tksfz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-18 20:47:16

Możesz użyć Typeable klasy typu z shapeless aby uzyskać wynik,

Przykładowa sesja 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))

Operacja cast będzie jak najdokładniejszym usunięciem wrt, biorąc pod uwagę dostępne przypadki w zakresie Typeable.

 65
Author: Miles Sabin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-11 15:55:14

Wymyśliłem stosunkowo proste rozwiązanie, które wystarczyłoby w sytuacjach o ograniczonym użyciu, zasadniczo owijając parametryzowane typy, które cierpiałyby z powodu problemu usuwania typów w klasach owijania, które mogą być używane w instrukcji match.

case class StringListHolder(list:List[String])

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

To ma oczekiwane wyjście i ogranicza zawartość naszej klasy case do żądanego typu, List łańcuchowych.

Więcej szczegółów tutaj: http://www.scalafied.com/?p=60

 17
Author: thricejamie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-14 21:29:24

Istnieje sposób na przezwyciężenie problemu usuwania typu w Scali. W przezwyciężenie wymazywania typu w dopasowaniu 1 oraz przezwyciężenie wymazywania typu w dopasowaniu 2 (wariancja) są jakieś wyjaśnienia, jak kodować niektóre helpery do zawijania typów, w tym wariancji, do dopasowania.

 14
Author: axaluss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-25 13:11:46

Znalazłem nieco lepsze obejście tego ograniczenia w inny sposób niesamowitego języka.

W Scali problem typu erasure nie występuje w przypadku tablic. Myślę, że łatwiej jest to zademonstrować na przykładzie.

Powiedzmy, że mamy listę (Int, String), wtedy poniżej podano Ostrzeżenie typu erasure

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

Aby to obejść, najpierw Utwórz klasę case:

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

Następnie w dopasowaniu wzorca zrób coś takiego:

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

Który wydaje się działać doskonale.

Wymaga to niewielkich zmian w kodzie do pracy z tablicami zamiast listami, ale nie powinno to stanowić większego problemu.

Zauważ, że użycie case a:Array[(Int, String)] nadal wyświetli ostrzeżenie o usunięciu typu, dlatego konieczne jest użycie nowej klasy kontenera(w tym przykładzie IntString).

 11
Author: Jus12,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-16 18:45:17

Ponieważ Java nie zna rzeczywistego typu elementu, najbardziej przydatne okazało się użycie List[_]. Wtedy Ostrzeżenie odchodzi, a kod opisuje rzeczywistość - jest to lista czegoś nieznanego.

 6
Author: rained_in,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-01 21:20:28

Zastanawiam się, czy jest to odpowiednie obejście:

scala> List(1,2,3) match {
     |    case List(_: String, _*) => println("A list of strings?!")
     |    case _ => println("Ok")
     | }

Nie pasuje do przypadku "pusta lista" , ale daje błąd kompilacji, a nie Ostrzeżenie!

error: type mismatch;
found:     String
requirerd: Int
To z drugiej strony wydaje się działać....
scala> List(1,2,3) match {
     |    case List(_: Int, _*) => println("A list of ints")
     |    case _ => println("Ok")
     | }
Czy to nie jest nawet lepsze, czy nie o to mi chodzi?
 4
Author: agilesteel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-12 09:36:52

Nie jest to rozwiązanie, ale sposób na życie bez zamiatania go pod dywan: Dodawanie adnotacji @unchecked. Zobacz tutaj - http://www.scala-lang.org/api/current/index.html#scala.unchecked

 1
Author: matanster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-06 18:23:29

Chciałem dodać odpowiedź, która uogólnia problem do: jak uzyskać reprezentację String typu mojej listy w czasie wykonywania

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))
 0
Author: Steve Robinson-Burns,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-11 08:50:40

Użycie pattern match guard

    list match  {
        case x:List if x.isInstanceOf(List[String]) => do sth
        case x:List if x.isInstanceOf(List[Int]) => do sth else
     }
 -19
Author: Huangmao Quan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-03 16:00:17