Ich habe dies in Scala geschrieben und es wird nicht kompiliert:
class TestDoubleDef{
def foo(p:List[String]) = {}
def foo(p:List[Int]) = {}
}
Der Compiler benachrichtigt:
[error] double definition:
[error] method foo:(List[String])Unit and
[error] method foo:(List[Int])Unit at line 120
[error] have same type after erasure: (List)Unit
Ich weiß, dass JVM keine native Unterstützung für Generika hat, daher verstehe ich diesen Fehler.
Ich könnte Wrapper schreiben für List[String]
und List[Int]
aber ich bin faul :)
Ich bin zweifelhaft, aber gibt es eine andere Art des Ausdrucks, List[String]
die nicht vom selben Typ ist wie List[Int]
?
Vielen Dank.
Antworten:
Ich mag die Idee von Michael Krämer, Implizite zu verwenden, aber ich denke, sie kann direkter angewendet werden:
case class IntList(list: List[Int]) case class StringList(list: List[String]) implicit def il(list: List[Int]) = IntList(list) implicit def sl(list: List[String]) = StringList(list) def foo(i: IntList) { println("Int: " + i.list)} def foo(s: StringList) { println("String: " + s.list)}
Ich denke, das ist gut lesbar und unkompliziert.
[Aktualisieren]
Es gibt noch einen anderen einfachen Weg, der zu funktionieren scheint:
def foo(p: List[String]) { println("Strings") } def foo[X: ClassTag](p: List[Int]) { println("Ints") } def foo[X: ClassTag, Y: ClassTag](p: List[Double]) { println("Doubles") }
Für jede Version benötigen Sie einen zusätzlichen Typparameter, damit dieser nicht skaliert, aber ich denke, für drei oder vier Versionen ist es in Ordnung.
[Update 2]
Für genau zwei Methoden habe ich einen weiteren schönen Trick gefunden:
def foo(list: => List[Int]) = { println("Int-List " + list)} def foo(list: List[String]) = { println("String-List " + list)}
quelle
foo(List l,Classmanifest cx)
und sichfoo(List l,Classmanifest cx, Classmanifest cy)
davon unterscheidetfoo(List l)
.case class
s, mit der von Jean-Philippe Pellet bereitgestellten Lösung vergleichen ?ClassManifest
ist eine Scala-Lösung vor 2.10, seitdem gibt esTypeTag
undClassTag
. Können Sie Ihre Lösung mit PLZ aktualisieren? Weitere Infos: docs.scala-lang.org/overviews/reflection/…Anstatt implizite Dummy-Werte zu erfinden, können Sie die
DummyImplicit
definierten Werte verwenden, inPredef
denen genau das gemacht zu sein scheint:class TestMultipleDef { def foo(p:List[String]) = () def foo(p:List[Int])(implicit d: DummyImplicit) = () def foo(p:List[java.util.Date])(implicit d1: DummyImplicit, d2: DummyImplicit) = () }
quelle
scala: ambiguous reference to overloaded definition, both method apply in class DBObjectExtension of type [A](key: String)(implicit d: DummyImplicit)List[A] and method apply in class DBObjectExtension of type [A](key: String)(implicit d1: DummyImplicit, implicit d2: DummyImplicit)A match argument types (String) and expected result type List[String] val zzzz : List[String] = place("cats") ^
Gedanken?String
als nicht implizites Argument. In meinen Beispielen unterscheidet sich der Typ des Parameters :List[String]
,List[Int]
undList[Date]
.case class
s basiert ?Um die Lösung von Michael Krämer zu verstehen , muss erkannt werden, dass die Typen der impliziten Parameter unwichtig sind. Was ist wichtig, dass ihre Typen unterscheiden.
Der folgende Code funktioniert genauso:
class TestDoubleDef { object dummy1 { implicit val dummy: dummy1.type = this } object dummy2 { implicit val dummy: dummy2.type = this } def foo(p:List[String])(implicit d: dummy1.type) = {} def foo(p:List[Int])(implicit d: dummy2.type) = {} } object App extends Application { val a = new TestDoubleDef() a.foo(1::2::Nil) a.foo("a"::"b"::Nil) }
Auf Bytecode-Ebene werden beide
foo
Methoden zu Methoden mit zwei Argumenten, da der JVM-Bytecode keine impliziten Parameter oder mehrere Parameterlisten kennt. Auf der Aufrufseite wählt der Scala-Compiler die entsprechendefoo
Methode zum Aufrufen (und damit das entsprechende Dummy-Objekt zum Übergeben ) aus, indem er den Typ der übergebenen Liste überprüft (die erst später gelöscht wird).Dieser Ansatz ist zwar ausführlicher, entlastet den Aufrufer jedoch von der Last, die impliziten Argumente anzugeben. Tatsächlich funktioniert es sogar, wenn die DummyN-Objekte für die
TestDoubleDef
Klasse privat sind .quelle
(implicit c: MyContext, d: dummy1.type)
Aufgrund der Wunder der Typlöschung werden die Typparameter der Methodenliste während der Kompilierung gelöscht, wodurch beide Methoden auf dieselbe Signatur reduziert werden. Dies ist ein Compilerfehler.
quelle
Wie Viktor Klang bereits sagt, wird der generische Typ vom Compiler gelöscht. Zum Glück gibt es eine Problemumgehung:
class TestDoubleDef{ def foo(p:List[String])(implicit ignore: String) = {} def foo(p:List[Int])(implicit ignore: Int) = {} } object App extends Application { implicit val x = 0 implicit val y = "" val a = new A() a.foo(1::2::Nil) a.foo("a"::"b"::Nil) }
Danke für Michid für den Tipp!
quelle
Wenn ich kombinieren Daniel s Antwort und Sandor Muraközi Antwort hier erhalte ich:
@annotation.implicitNotFound(msg = "Type ${T} not supported only Int and String accepted") sealed abstract class Acceptable[T]; object Acceptable { implicit object IntOk extends Acceptable[Int] implicit object StringOk extends Acceptable[String] } class TestDoubleDef { def foo[A : Acceptable : Manifest](p:List[A]) = { val m = manifest[A] if (m equals manifest[String]) { println("String") } else if (m equals manifest[Int]) { println("Int") } } }
Ich bekomme eine typsichere (ish) Variante
scala> val a = new TestDoubleDef a: TestDoubleDef = TestDoubleDef@f3cc05f scala> a.foo(List(1,2,3)) Int scala> a.foo(List("test","testa")) String scala> a.foo(List(1L,2L,3L)) <console>:21: error: Type Long not supported only Int and String accepted a.foo(List(1L,2L,3L)) ^ scala> a.foo("test") <console>:9: error: type mismatch; found : java.lang.String("test") required: List[?] a.foo("test") ^
Die Logik kann auch als solche in die Typklasse aufgenommen werden (dank jsuereth ): @ annotation.implicitNotFound (msg = "Foo unterstützt nicht nur $ {T} nur Int und String akzeptiert") versiegeltes Merkmal Foo [T] {def apply (Liste: Liste [T]): Einheit}
object Foo { implicit def stringImpl = new Foo[String] { def apply(list : List[String]) = println("String") } implicit def intImpl = new Foo[Int] { def apply(list : List[Int]) = println("Int") } } def foo[A : Foo](x : List[A]) = implicitly[Foo[A]].apply(x)
Welches gibt:
scala> @annotation.implicitNotFound(msg = "Foo does not support ${T} only Int and String accepted") | sealed trait Foo[T] { def apply(list : List[T]) : Unit }; object Foo { | implicit def stringImpl = new Foo[String] { | def apply(list : List[String]) = println("String") | } | implicit def intImpl = new Foo[Int] { | def apply(list : List[Int]) = println("Int") | } | } ; def foo[A : Foo](x : List[A]) = implicitly[Foo[A]].apply(x) defined trait Foo defined module Foo foo: [A](x: List[A])(implicit evidence$1: Foo[A])Unit scala> foo(1) <console>:8: error: type mismatch; found : Int(1) required: List[?] foo(1) ^ scala> foo(List(1,2,3)) Int scala> foo(List("a","b","c")) String scala> foo(List(1.0)) <console>:32: error: Foo does not support Double only Int and String accepted foo(List(1.0)) ^
Beachten Sie, dass wir schreiben müssen,
implicitly[Foo[A]].apply(x)
da der Compiler denkt, dass diesimplicitly[Foo[A]](x)
bedeutet, dass wirimplicitly
mit Parametern aufrufen .quelle
Es gibt (mindestens einen) anderen Weg, auch wenn er nicht zu schön und nicht wirklich typsicher ist:
import scala.reflect.Manifest object Reified { def foo[T](p:List[T])(implicit m: Manifest[T]) = { def stringList(l: List[String]) { println("Strings") } def intList(l: List[Int]) { println("Ints") } val StringClass = classOf[String] val IntClass = classOf[Int] m.erasure match { case StringClass => stringList(p.asInstanceOf[List[String]]) case IntClass => intList(p.asInstanceOf[List[Int]]) case _ => error("???") } } def main(args: Array[String]) { foo(List("String")) foo(List(1, 2, 3)) } }
Der implizite Manifest-Parameter kann verwendet werden, um den gelöschten Typ zu "reifizieren" und somit das Löschen zu umgehen. Sie können in vielen Blog-Posts, z . B. in diesem, etwas mehr darüber erfahren .
Was passiert ist, dass der Manifest-Parameter Ihnen zurückgeben kann, was T vor dem Löschen war. Dann erledigt ein einfacher Versand basierend auf T an die verschiedenen realen Implementierungen den Rest.
Wahrscheinlich gibt es eine schönere Möglichkeit, den Mustervergleich durchzuführen, aber ich habe ihn noch nicht gesehen. Was die Leute normalerweise tun, ist Matching auf m.toString, aber ich denke, Klassen zu halten ist ein bisschen sauberer (auch wenn es ein bisschen ausführlicher ist). Leider ist die Dokumentation von Manifest nicht zu detailliert, vielleicht hat sie auch etwas, das sie vereinfachen könnte.
Ein großer Nachteil davon ist, dass es nicht wirklich typsicher ist: foo wird mit jedem T zufrieden sein, wenn Sie nicht damit umgehen können, haben Sie ein Problem. Ich denke, es könnte mit einigen Einschränkungen für T umgangen werden, aber es würde es weiter erschweren.
Und natürlich ist das ganze Zeug auch nicht zu schön, ich bin mir nicht sicher, ob es sich lohnt, es zu tun, besonders wenn du faul bist ;-)
quelle
Anstelle von Manifesten können Sie auch Dispatcher-Objekte verwenden, die implizit auf ähnliche Weise importiert wurden. Ich habe darüber gebloggt, bevor Manifeste auftauchten: http://michid.wordpress.com/code/implicit-double-dispatch-revisited/
Dies hat den Vorteil der Typensicherheit: Die überladene Methode kann nur für Typen aufgerufen werden, bei denen Disponenten in den aktuellen Bereich importiert wurden.
quelle
Netter Trick, den ich aus http://scala-programming-language.1934581.n4.nabble.com/disambiguation-of-double-definition-resulting-from-generic-type-erasure-td2327664.html von Aaron Novstrup gefunden habe
object Baz { private object dummy1 { implicit val dummy: dummy1.type = this } private object dummy2 { implicit val dummy: dummy2.type = this } def foo(xs: String*)(implicit e: dummy1.type) = 1 def foo(xs: Int*)(implicit e: dummy2.type) = 2 }
[...]
quelle
Ich habe versucht, die Antworten von Aaron Novstrup und Leo zu verbessern, um einen Satz von Standard-Beweisobjekten importierbar und knapper zu machen.
final object ErasureEvidence { class E1 private[ErasureEvidence]() class E2 private[ErasureEvidence]() implicit final val e1 = new E1 implicit final val e2 = new E2 } import ErasureEvidence._ class Baz { def foo(xs: String*)(implicit e:E1) = 1 def foo(xs: Int*)(implicit e:E2) = 2 }
Dies führt jedoch dazu, dass sich der Compiler beschwert, dass für den impliziten Wert mehrdeutige Auswahlmöglichkeiten bestehen, wenn
foo
eine andere Methode aufgerufen wird, für die ein impliziter Parameter desselben Typs erforderlich ist.Daher biete ich nur das Folgende an, was in einigen Fällen knapper ist. Und diese Verbesserung funktioniert mit Wertklassen (denen, die
extend AnyVal
).final object ErasureEvidence { class E1[T] private[ErasureEvidence]() class E2[T] private[ErasureEvidence]() implicit def e1[T] = new E1[T] implicit def e2[T] = new E2[T] } import ErasureEvidence._ class Baz { def foo(xs: String*)(implicit e:E1[Baz]) = 1 def foo(xs: Int*)(implicit e:E2[Baz]) = 2 }
Wenn der enthaltende Typname ziemlich lang ist, deklarieren Sie einen inneren
trait
, um ihn knapper zu machen.class Supercalifragilisticexpialidocious[A,B,C,D,E,F,G,H,I,J,K,L,M] { private trait E def foo(xs: String*)(implicit e:E1[E]) = 1 def foo(xs: Int*)(implicit e:E2[E]) = 2 }
Wertklassen erlauben jedoch keine inneren Merkmale, Klassen oder Objekte. Beachten Sie daher auch, dass die Antworten von Aaron Novstrup und Leo nicht mit Wertklassen funktionieren.
quelle
Ich habe das nicht getestet, aber warum sollte eine Obergrenze nicht funktionieren?
def foo[T <: String](s: List[T]) { println("Strings: " + s) } def foo[T <: Int](i: List[T]) { println("Ints: " + i) }
Wechselt die Löschübersetzung zweimal von foo (Liste [Beliebig]) zu foo (Liste [Zeichenfolge]) und foo (Liste [Int] i):
http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ108
Ich glaube, ich habe gelesen, dass in Version 2.8 die Obergrenzen jetzt so codiert sind, anstatt immer ein Any.
Um kovariante Typen zu überladen, verwenden Sie eine invariante Grenze (gibt es eine solche Syntax in Scala? ... ah, ich glaube nicht, aber nehmen Sie Folgendes als konzeptionellen Nachtrag zur obigen Hauptlösung):
def foo[T : String](s: List[T]) { println("Strings: " + s) } def foo[T : String2](s: List[T]) { println("String2s: " + s) }
dann nehme ich an, dass das implizite Casting in der gelöschten Version des Codes beseitigt ist.
UPDATE: Das Problem ist, dass JVM mehr Typinformationen zu Methodensignaturen löscht als "notwendig". Ich habe einen Link bereitgestellt. Es löscht Typvariablen aus Typkonstruktoren, sogar die konkrete Grenze dieser Typvariablen. Es gibt eine konzeptionelle Unterscheidung, da das Löschen des gebundenen Funktionstyps keinen konzeptionellen, nicht reifizierten Vorteil hat, da er zur Kompilierungszeit bekannt ist und sich mit keiner Instanz des Generikums ändert und Anrufer nicht aufrufen müssen Die Funktion mit Typen, die nicht der Typbindung entsprechen. Wie kann die JVM die Typbindung erzwingen, wenn sie gelöscht wird? Gut ein Linksagt, dass der gebundene Typ in Metadaten beibehalten wird, auf die Compiler zugreifen sollen. Dies erklärt, warum die Verwendung von Typgrenzen keine Überladung ermöglicht. Dies bedeutet auch, dass JVM eine weit offene Sicherheitslücke ist, da typgebundene Methoden ohne Typgrenzen aufgerufen werden können (Huch!). Entschuldigen Sie mich, dass die JVM-Designer so etwas unsicheres nicht tun würden.
Als ich das schrieb, verstand ich nicht, dass Stackoverflow ein System ist, bei dem Menschen nach der Qualität der Antworten bewertet werden, wie etwa ein Wettbewerb um den Ruf. Ich dachte, es wäre ein Ort, um Informationen auszutauschen. Zu der Zeit, als ich dies schrieb, verglich ich reifiziertes und nicht-reifiziertes von einer konzeptionellen Ebene (Vergleich vieler verschiedener Sprachen), und daher machte es meiner Meinung nach keinen Sinn, den gebundenen Typ zu löschen.
quelle