390 Stimmen

Wie umgehe ich die Typ-Löschung in Scala? Oder, warum kann ich nicht den Typ-Parameter meiner Sammlungen erhalten?

Es ist eine traurige Tatsache des Lebens auf Scala, dass, wenn Sie eine List[Int] instanziieren, können Sie überprüfen, dass Ihre Instanz eine Liste ist, und Sie können überprüfen, dass jedes einzelne Element davon ein Int ist, aber nicht, dass es eine List[Int] ist, wie leicht überprüft werden kann:

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

Mit der Option -unchecked wird die Schuld direkt auf das Löschen des Typs geschoben:

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

Warum ist das so, und wie kann ich das Problem umgehen?

244voto

Daniel C. Sobral Punkte 290004

Diese Antwort verwendet die Manifest -API, die ab Scala 2.10 veraltet ist. Bitte lesen Sie die Antworten unten für aktuellere Lösungen.

Scala wurde mit Type Erasure definiert, weil die Java Virtual Machine (JVM), im Gegensatz zu Java, keine Generics erhalten hat. Das bedeutet, dass zur Laufzeit nur die Klasse existiert, nicht aber ihre Typparameter. In diesem Beispiel weiß die JVM, dass sie mit einer scala.collection.immutable.List , aber nicht, dass diese Liste parametrisiert ist mit Int .

Glücklicherweise gibt es in Scala eine Funktion, mit der Sie das umgehen können. Es ist die Manifest . Ein Manifest ist eine Klasse, deren Instanzen Objekte sind, die Typen darstellen. Da es sich bei diesen Instanzen um Objekte handelt, können Sie sie weitergeben, speichern und im Allgemeinen Methoden für sie aufrufen. Mit der Unterstützung von impliziten Parametern wird es zu einem sehr mächtigen Werkzeug. Nehmen Sie zum Beispiel das folgende Beispiel:

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

Wenn wir ein Element speichern, speichern wir auch ein "Manifest" davon. Ein Manifest ist eine Klasse, deren Instanzen Scala-Typen darstellen. Diese Objekte verfügen über mehr Informationen als die JVM, die es uns ermöglichen, auf den vollständigen, parametrisierten Typ zu testen.

Beachten Sie jedoch, dass eine Manifest ist ein noch zu entwickelndes Merkmal. Ein Beispiel für seine Grenzen ist, dass es derzeit nichts über Varianz weiß und davon ausgeht, dass alles kovariant ist. Ich erwarte, dass es stabiler und solider wird, sobald die Scala-Reflexionsbibliothek, die derzeit entwickelt wird, fertiggestellt ist.

104voto

tksfz Punkte 2872

Sie können dies mit TypeTags tun (wie Daniel bereits erwähnt hat, aber ich werde es noch einmal explizit erklären):

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

Sie können dies auch mit ClassTags tun (was Ihnen die Abhängigkeit von scala-reflect erspart):

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 können verwendet werden, solange Sie nicht erwarten, dass der Typparameter A selbst ein generischer Typ sein.

Leider ist es etwas langatmig und man braucht die @unchecked-Anmerkung, um eine Compilerwarnung zu unterdrücken. Der TypeTag kann in Zukunft vom Compiler automatisch in den Mustervergleich einbezogen werden: https://issues.scala-lang.org/browse/SI-6517

64voto

Miles Sabin Punkte 22925

Sie können die Typeable Typklasse aus Unförmig um das gewünschte Ergebnis zu erzielen,

Beispiel einer REPL-Sitzung,

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

En cast der Vorgang wird so präzise wie möglich sein, wenn man bedenkt, dass er in den Geltungsbereich fällt Typeable Instanzen verfügbar.

17voto

thricejamie Punkte 179

Ich habe eine relativ einfache Lösung gefunden, die in Situationen mit eingeschränktem Nutzen ausreicht, indem ich parametrisierte Typen, die unter dem Problem der Typenlöschung leiden würden, in Wrapper-Klassen verpacke, die in einer Match-Anweisung verwendet werden können.

case class StringListHolder(list:List[String])

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

Dies hat die erwartete Ausgabe und beschränkt den Inhalt unserer Fallklasse auf den gewünschten Typ, String Lists.

Weitere Einzelheiten finden Sie hier: http://www.scalafied.com/?p=60

13voto

Alex Punkte 7538

Es gibt eine Möglichkeit, das Problem der Typenlöschung in Scala zu lösen. In Überwindung der Typenlöschung bei der Anpassung 1 y Überwindung der Typenlöschung in Matching 2 (Varianz) sind einige Erklärungen, wie man einige Helfer kodiert, um die Typen, einschließlich Varianz, für den Abgleich zu verpacken.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X