it-swarm.com.de

Wie komme ich mit Scala beim Löschen von Typen um? Oder warum kann ich den Typparameter meiner Sammlungen nicht abrufen?

Es ist eine traurige Tatsache im Leben von Scala, dass Sie, wenn Sie eine Liste [Int] instanziieren, überprüfen können, ob Ihre Instanz eine Liste ist, und Sie können überprüfen, ob jedes einzelne Element davon ein Int ist, nicht jedoch, dass es eine Liste ist. Int], wie leicht zu überprüfen ist:

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 das Löschen von Typen direkt angezeigt:

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 komme ich damit um?

359

Diese Antwort verwendet die Manifest- API, die seit Scala 2.10 nicht mehr empfohlen wird. Weitere aktuelle Lösungen finden Sie in den folgenden Antworten.

Scala wurde mit Type Erasure definiert, da die Java Virtual Machine (JVM) im Gegensatz zu Java keine Generics erhielt. Dies bedeutet, dass zur Laufzeit nur die Klasse vorhanden ist, nicht die Typparameter. In dem Beispiel weiß die JVM, dass sie einen scala.collection.immutable.List verarbeitet, jedoch nicht, dass diese Liste mit Int parametrisiert wird.

Glücklicherweise gibt es in Scala eine Funktion, mit der Sie das umgehen können. Es ist das 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 generell Methoden aufrufen. Mit der Unterstützung impliziter Parameter wird es zu einem sehr leistungsfähigen Werkzeug. Nehmen wir zum Beispiel folgendes 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 Sie ein Element speichern, speichern wir auch ein "Manifest" davon. Ein Manifest ist eine Klasse, deren Instanzen Scala-Typen darstellen. Diese Objekte enthalten mehr Informationen als JVM, sodass der vollständige parametrisierte Typ getestet werden kann.

Beachten Sie jedoch, dass eine Manifest immer noch eine sich entwickelnde Funktion ist. Als ein Beispiel für seine Begrenzungen kennt es derzeit nichts über die Varianz und nimmt an, dass alles gleich Variante ist. Ich gehe davon aus, dass es stabiler und solider wird, sobald die Scala-Reflektionsbibliothek, die sich derzeit in der Entwicklung befindet, fertig ist.

239

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

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 (wodurch Sie sich nicht auf scala-reflect verlassen müssen):

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 davon ausgehen, dass der Typparameter A selbst ein generischer Typ ist.

Leider ist es etwas wortreich und Sie benötigen die @ unchecked-Annotation, um eine Compiler-Warnung zu unterdrücken. Das TypeTag kann in Zukunft vom Compiler automatisch in die Musterübereinstimmung integriert werden: https://issues.scala-lang.org/browse/SI-6517

92
tksfz

Sie können die Typklasse Typeable von shapeless verwenden, um das gewünschte Ergebnis zu erhalten.

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

Die cast-Operation wird so genau wie möglich gelöscht, wenn die Typeable-Instanzen im Gültigkeitsbereich verfügbar sind.

61
Miles Sabin

Ich habe eine relativ einfache Lösung gefunden, die in Situationen mit eingeschränkter Nutzung ausreicht. Im Wesentlichen werden parametrisierte Typen umschlossen, die unter Typ-Löschungsproblemen in Wrapper-Klassen leiden, 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 Details hier: http://www.scalafied.com/?p=60

15
thricejamie

Es gibt einen Weg, um das Problem der Typenlöschung in Scala zu überwinden. In Überwindung der Typenlöschung im Matching 1 und Überwindung der Typlöschung in Matching 2 (Varianz) werden einige Anweisungen zum Codieren einiger Helfer zum Einschließen der Typen, einschließlich Varianz zum Abgleichen. 

14
Alex

Ich fand eine etwas bessere Lösung für diese Einschränkung der ansonsten genialen Sprache. 

In Scala tritt das Problem der Typenlöschung bei Arrays nicht auf. Ich denke, es ist einfacher, dies an einem Beispiel zu zeigen. 

Nehmen wir an, wir haben eine Liste von (Int, String), dann wird im Folgenden eine Warnung zum Löschen des Typs angezeigt

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

Um dies zu umgehen, erstellen Sie zunächst eine Fallklasse:

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

dann im Mustervergleich so etwas tun:

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

das scheint perfekt zu funktionieren. 

Dies erfordert geringfügige Änderungen in Ihrem Code, um mit Arrays anstelle von Listen zu arbeiten, sollte jedoch kein großes Problem sein.

Beachten Sie, dass bei Verwendung von case a:Array[(Int, String)] immer noch eine Warnung zum Löschen des Typs ausgegeben wird. Daher muss eine neue Containerklasse verwendet werden (in diesem Beispiel IntString).

11
Jus12

Da Java den eigentlichen Elementtyp nicht kennt, war es am nützlichsten, einfach List[_] zu verwenden. Dann geht die Warnung weg und der Code beschreibt die Realität - es ist eine Liste von etwas Unbekanntem.

6
rained_in

Ich frage mich, ob dies eine geeignete Problemumgehung ist:

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

Es stimmt nicht mit dem Fall "leere Liste" überein, aber es wird ein Kompilierungsfehler ausgegeben, keine Warnung!

error: type mismatch;
found:     String
requirerd: Int

Dies scheint andererseits zu funktionieren ....

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

Ist es nicht irgendwie besser oder verpasst ich den Punkt hier?

4
agilesteel

Keine Lösung, aber eine Möglichkeit, damit zu leben, ohne sie ganz unter den Teppich zu kehren: Hinzufügen der @unchecked-Anmerkung. Siehe hier - http://www.scala-lang.org/api/current/index.html#scala.unchecked

1
matanster

Ich wollte eine Antwort hinzufügen, die das Problem verallgemeinert: Wie erhält man eine String-Darstellung des Typs meiner Liste zur Laufzeit?

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
Steve Robinson