Scala Type Parameter

planetenkiller

Aktives Mitglied
Ich bin seit Stunden dabei, folgendes in Scala zum laufen zu bringen (Beispiel was mein Problem zeigt):
Code:
object Demo {
    def main(args: Array[String]): Unit = {
        val converter: Map[String, Converter[Any]] = Map("Int" -> new IntConverter()) // Zeile 3
        
        // value muss den Type Any haben. Methoden wie supports() habe ich mal weg gelassen
        val value: Any = 1
        println(converter("Int").convert(value))
    }
}

trait Converter[I] {
    def convert(in: I): String  // Zeile 12
}

class IntConverter extends Converter[Int] {
    def convert(in: Int): String = in.toString()
}

Der obige Code erzeugt folgenden Fehler:
type mismatch; found : IntConverter required: Converter[Any] Note: Int <: Any (and IntConverter <: Converter[Int]), but trait Converter is invariant in type I. You may wish to define I as +I instead. (SLS 4.5)
(Fehler bei Zeile 3)

aus kann ich auch kein [+] ich machen wegen
covariant type I occurs in contravariant position in type I of value in Demo.scala
(Fehler bei Zeile 12)

bei [-I]
type mismatch; found : IntConverter required: Converter[Any]
(Fehler bei Zeile 3)

Jemand eine Idee wie das zum laufen kriege?
 

Antoras

Top Contributor
Das ist ein typisches Varianzen-Problem. Warum das nicht funktioniert ist schwer zu erklären, weil man davor erst den Sinn von Varianzen verstanden haben sollte.

Ich hab für mein Scala Tutorial mal angefangen einen Beitrag über Varianzen zu schreiben, bin bisher aber nicht dazu gekommen ihn fertig zu stellen, weshalb er noch nicht öffentlich zugänglich ist. Vllt. bekomme ich das die nächsten Tage hin, dann werde ich dir den Link dazu schicken.

Aber jetzt zu deinem Problem: Das was du vorhast wird nie funktionieren. Der Compiler kann nicht garantieren, dass deine Map einen Converter zurück gibt, der mit dem übergebenen Typ klarkommt.

Wenn der Compiler das zur Compilezeit aber garantieren soll (also so, dass du zur Laufzeit nicht casten musst), dann ist es möglich, das in Scala über impliziter Parameter zu regeln:

Code:
object Converts {

  trait Converter[A] {
    def convert(in: A): String
  }

  implicit object IntConverter extends Converter[Int] {
    def convert(in: Int): String = in.toString
  }

  implicit object DoubleConverter extends Converter[Double] {
    def convert(in: Double): String = in.toString
  }

  def convert[A](x: A)(implicit c: Converter[A]) = c.convert(x)
}

import Converts._

convert(3)
convert(6.48)
convert("does not work")
 
Zuletzt bearbeitet:

planetenkiller

Aktives Mitglied
Der Compiler kann nicht garantieren, dass deine Map einen Converter zurück gibt, der mit dem übergebenen Typ klarkommt.

Ja, daran dachte ich auch schon. Ich möchte dem Compiler nur irgendwie sagen:
Ich weiss das es keinen Runtime Fehler (ClassCastException) geben wird (oder es ist mir egal), ignoriere den Fehler.

*Edit*:
implicit geht, soweit ich das verstehe, ja nur, wenn ich eine Variable mit einem Konkreten Typ habe. In meinem Fall habe ich aber nur Any (kann ich auch nicht ändern).
 
Zuletzt bearbeitet:

Antoras

Top Contributor
Was du suchst ist "multiple dispatch", was grundsätzlich immer mit Typüberprüfungen zur Laufzeit verbunden ist (afaik). Da es in Scala keinen in die Sprache eingebauten "dispatch" gibt, wirst du nicht drum herum kommen, als selbst eine Typüberprüfung zu schreiben:
Code:
convert(a: Any) = a match {
  case a: Int => IntConverter.convert(a)
  case f: Float => FloatConverter.convert(a)
  ...
}
 

planetenkiller

Aktives Mitglied
Workaround den ich gefunden habe:
Code:
object Demo {
    def main(args: Array[String]): Unit = {
        val converter: Map[String, Converter[_]] = Map("Int" -> new IntConverter())
        
        val value: AnyRef = java.lang.Integer.valueOf(1)
        println(Class.forName("Converter").getDeclaredMethod("convert", Class.forName("java.lang.Object")).invoke(converter("Int"), value))
    }
}

trait Converter[I] {
    def convert(in: I): String
}

class IntConverter extends Converter[Int] {
    def convert(in: Int): String = in.toString()
}
:(

match bringt mir nichts, weil ich nicht alle Typen vorher kenne
 

planetenkiller

Aktives Mitglied
Danke, der Artikel hat mir sehr weitergeholfen. Ich konnte eine Lösung finden:

Code:
object Demo {
    def main(args: Array[String]): Unit = {
        val converter: Map[String, Converter[Any]] = Map("Int" -> new IntConverter())
        
        val value: Any = 1
        println(converter("Int").convert(value))
    }
}

trait Converter[+I] {
    def convert[J >: I](in: J): String 
}

class IntConverter extends Converter[Int] {
    def convert[A >: Int](in: A): String = in.toString()
}
 

Antoras

Top Contributor
Ich hatte zu erst vor dir das vorzuschlagen, bin aber dann davon abgekommen, weil es nicht möglich ist die Parametrisierung in den erbenden Klassen zu spezialisieren. Du kannst beliebige Typen (Any) an die Methode convert übergeben - auch an den IntConverter. Im Grunde kannst du die Parametrisierung komplett weglassen und die Methode gleich ein Any erwarten lassen.
 
Ähnliche Java Themen

Ähnliche Java Themen

Neue Themen


Oben