Eine Möglichkeit , dass hat vorgeschlagen, mit Doppel Definitionen überladener Methoden zu behandeln ist zu ersetzen durch Musterabgleich Überlastung:
object Bar {
def foo(xs: Any*) = xs foreach {
case _:String => println("str")
case _:Int => println("int")
case _ => throw new UglyRuntimeException()
}
}
Dieser Ansatz erfordert, dass wir die statische Typprüfung der Argumente an übergeben foo
. Es wäre viel schöner, schreiben zu können
object Bar {
def foo(xs: (String or Int)*) = xs foreach {
case _: String => println("str")
case _: Int => println("int")
}
}
Ich kann nah dran sein Either
, aber es wird schnell hässlich mit mehr als zwei Typen:
type or[L,R] = Either[L,R]
implicit def l2Or[L,R](l: L): L or R = Left(l)
implicit def r2Or[L,R](r: R): L or R = Right(r)
object Bar {
def foo(xs: (String or Int)*) = xs foreach {
case Left(l) => println("str")
case Right(r) => println("int")
}
}
Es sieht aus wie ein General (elegant, effizient) Lösung wäre die Definition erfordern Either3
, Either4
.... Kennt jemand eine alternative Lösung , die das gleiche Ziel zu erreichen? Meines Wissens hat Scala keine eingebaute "Typdisjunktion". Lauern die oben definierten impliziten Konvertierungen irgendwo in der Standardbibliothek, sodass ich sie einfach importieren kann?
class StringOrInt[T]
istsealed
, wird das von Ihnen erwähnte "Leck" ("Natürlich kann dies durch das Erstellen einesStringOrInt[Boolean]
" durch den Client-Code umgangen werden ) verstopft, zumindest wenn esStringOrInt
sich in einer eigenen Datei befindet. Dann müssen die Zeugenobjekte in derselben Quelle wie definiert werdenStringOrInt
.Either
Ansatz scheint zu sein, dass wir viel Compiler-Unterstützung für die Überprüfung der Übereinstimmung verlieren.trait StringOrInt ...
StringOrInt[T]
zuStringOrInt[-T]
(siehe stackoverflow.com/questions/24387701/… )Miles Sabin beschreibt in seinem kürzlich veröffentlichten Blog-Beitrag Unboxed Union Types in Scala über den Curry-Howard-Isomorphismus einen sehr guten Weg, um Gewerkschaftstypen zu erhalten :
Er definiert zunächst die Negation von Typen als
Mit dem Gesetz von De Morgan kann er Gewerkschaftstypen definieren
Mit den folgenden Hilfskonstrukten
Sie können Unionstypen wie folgt schreiben:
quelle
Dotty , ein neuer experimenteller Scala-Compiler, unterstützt Unionstypen (geschrieben
A | B
), sodass Sie genau das tun können, was Sie wollten:quelle
Hier ist die Rex Kerr-Methode zum Codieren von Vereinigungstypen. Geradlinig und einfach!
Quelle: Kommentar Nr. 27 unter diesem ausgezeichneten Blog-Beitrag von Miles Sabin, der eine weitere Möglichkeit zum Codieren von Unionstypen in Scala bietet.
quelle
scala> f(9.2: AnyVal)
Übergibt den Typechecker.trait Contra[-A] {}
anstelle aller Funktionen nichts verwenden. So bekommt man Sachen wietype Union[A,B] = { type Check[Z] = Contra[Contra[Z]] <:< Contra[Contra[A] with Contra[B]] }
gebraucht wiedef f[T: Union[Int, String]#Check](t: T) = t match { case i: Int => i; case s: String => s.length }
(ohne ausgefallenen Unicode).Es ist möglich, Daniels Lösung wie folgt zu verallgemeinern :
Die Hauptnachteile dieses Ansatzes sind
Either
Ansatz würde weitere Verallgemeinerung erfordert analog zu definierenOr3
,Or4
usw. Merkmale. Das Definieren solcher Merkmale wäre natürlich viel einfacher als das Definieren der entsprechendenEither
Klassen.Aktualisieren:
Mitch Blevins demonstriert einen sehr ähnlichen Ansatz und zeigt, wie man ihn auf mehr als zwei Typen verallgemeinert, indem man ihn als "Stottern oder" bezeichnet.
quelle
Ich bin auf eine relativ saubere Implementierung von n-ary Union-Typen gestoßen, indem ich den Begriff der Typlisten mit einer Vereinfachung von Miles Sabins Arbeit in diesem Bereich kombiniert habe , die jemand in einer anderen Antwort erwähnt.
Gegebener Typ,
¬[-A]
der kontravariant istA
,A <: B
können wir per Definition schreiben¬[B] <: ¬[A]
und die Reihenfolge der Typen umkehren.Gegebene Typen
A
,B
undX
, möchten wir ausdrückenX <: A || X <: B
. Wenn wir Kontravarianz anwenden, bekommen wir¬[A] <: ¬[X] || ¬[B] <: ¬[X]
. Dies kann wiederum so ausgedrückt werden,¬[A] with ¬[B] <: ¬[X]
dass einer vonA
oderB
ein Supertyp vonX
oder sein mussX
selbst sein muss (denken Sie an Funktionsargumente).Ich habe einige Zeit damit verbracht, diese Idee mit einer Obergrenze für Elementtypen zu kombinieren, wie in
TList
s von harrah / up zu sehen , aber die Implementierung vonMap
mit Typgrenzen hat sich bisher als schwierig erwiesen.quelle
Function1
als vorhandener kontravarianter Typ verwendet. Sie benötigen keine Implementierung, Sie benötigen lediglich einen Konformitätsnachweis (<:<
).Eine Typklassenlösung ist wahrscheinlich der schönste Weg, um implizite Ergebnisse zu erzielen. Dies ähnelt dem im Buch Odersky / Spoon / Venners erwähnten monoiden Ansatz:
Wenn Sie dies dann in der REPL ausführen:
quelle
Either
Typ verstärkt diesen Glauben. Die Verwendung von Typklassen über Scalas Implikits ist eine bessere Lösung für das zugrunde liegende Problem, aber es ist ein relativ neues Konzept und noch nicht allgemein bekannt, weshalb das OP nicht einmal wusste, sie als mögliche Alternative zu einem Unionstyp zu betrachten.Wir möchten einen Typoperator
Or[U,V]
, mit dem TypparameterX
so eingeschränkt werden können, dass entwederX <: U
oderX <: V
. Hier ist eine Definition, die so nah wie möglich kommt:So wird es verwendet:
Dies verwendet einige Scala-Tricks. Das wichtigste ist die Verwendung verallgemeinerter Typbeschränkungen . Bei gegebenen Typen
U
undV
stellt der Scala-Compiler eine Klasse namensU <:< V
(und ein implizites Objekt dieser Klasse) bereit , wenn und nur wenn der Scala-Compiler nachweisen kann, dassU
es sich um einen Subtyp von handeltV
. Hier ist ein einfacheres Beispiel mit allgemeinen Typeinschränkungen, das in einigen Fällen funktioniert:Dieses Beispiel funktioniert, wenn
X
eine Instanz der KlasseB
aString
oder einen Typ hat, der weder ein Supertyp noch ein Subtyp vonB
oder istString
. In den ersten beiden Fällen ist es durch die Definition deswith
Schlüsselworts wahr, dass(B with String) <: B
und(B with String) <: String
, so dass Scala ein implizites Objekt bereitstellt, das übergeben wird alsev
: Der Scala-Compiler akzeptiertfoo[B]
und korrektfoo[String]
.Im letzten Fall verlasse ich mich auf die Tatsache, dass wenn
U with V <: X
, dannU <: X
oderV <: X
. Es scheint intuitiv wahr zu sein, und ich gehe einfach davon aus. Aus dieser Annahme geht hervor, warum dieses einfache Beispiel fehlschlägt, wennX
ein Supertyp oder Subtyp von entwederB
oderString
: im obigen Beispielfoo[A]
falsch akzeptiert undfoo[C]
falsch abgelehnt wird. Auch hier , was wir wollen , ist eine Art von Typ - Expression auf den VariablenU
,V
undX
das stimmt genau , wannX <: U
oderX <: V
.Scalas Begriff der Kontravarianz kann hier helfen. Erinnerst
trait Inv[-X]
du dich an das Merkmal ? Weil es in seinem Typparameter genau dann kontravariant istX
,Inv[X] <: Inv[Y]
wennY <: X
. Das bedeutet, dass wir das obige Beispiel durch eines ersetzen können, das tatsächlich funktioniert:Das liegt daran, dass der Ausdruck
(Inv[U] with Inv[V]) <: Inv[X]
nach der gleichen Annahme oben genau wannInv[U] <: Inv[X]
oderInv[V] <: Inv[X]
und nach der Definition der Kontravarianz genau dann wahr ist, wennX <: U
oderX <: V
.Es ist möglich, die Wiederverwendbarkeit zu verbessern, indem ein parametrierbarer Typ deklariert
BOrString[X]
und wie folgt verwendet wird:Scala versucht nun , den Typen zu konstruieren ,
BOrString[X]
für jedenX
, derfoo
mit genannt wird, und die Art genau gebaut werden , wennX
ein Subtyp von entwederB
oderString
. Das funktioniert und es gibt eine Kurzschreibweise. Die folgende Syntax ist äquivalent (mit der Ausnahme, dassev
jetzt im Methodenkörperimplicitly[BOrString[X]]
als einfach darauf verwiesen werden mussev
) und wirdBOrString
als Typkontext gebunden verwendet :Was wir wirklich möchten, ist eine flexible Möglichkeit, einen Typkontext gebunden zu erstellen. Ein Typkontext muss ein parametrisierbarer Typ sein, und wir möchten einen parametrisierbaren Weg, um einen zu erstellen. Das klingt so, als würden wir versuchen, Funktionen für Typen zu curry, genauso wie wir Funktionen für Werte curryen. Mit anderen Worten, wir möchten so etwas wie das Folgende:
Das ist in Scala nicht direkt möglich , aber es gibt einen Trick, mit dem wir uns ziemlich nahe kommen können. Das bringt uns zur
Or
obigen Definition :Hier verwenden wir die strukturelle Typisierung und den Pfund-Operator von Scala , um einen strukturellen Typ zu erstellen
Or[U,T]
, der garantiert einen internen Typ hat. Das ist ein seltsames Tier. Um einen Kontext zu geben,def bar[X <: { type Y = Int }](x : X) = {}
muss die Funktion mit Unterklassen aufgerufen werdenAnyRef
, in denen ein TypY
definiert ist:Wenn wir den Pfund-Operator verwenden, können wir uns auf den inneren Typ beziehen
Or[B, String]#pf
, und wennOr
wir die Infix-Notation für den Typ-Operator verwenden , gelangen wir zu unserer ursprünglichen Definition vonfoo
:Wir können die Tatsache nutzen, dass Funktionstypen in ihrem ersten Typparameter kontravariant sind, um zu vermeiden, dass das Merkmal definiert wird
Inv
:quelle
A|B <: A|B|C
Problem lösen ? stackoverflow.com/questions/45255270/… Ich kann es nicht sagen.Es gibt auch diesen Hack :
Siehe Umgehen von Mehrdeutigkeiten beim Löschen von Typen (Scala) .
quelle
(implicit e: DummyImplicit)
eine der Typensignaturen hinzu.Sie können sich MetaScala ansehen , das so etwas wie heißt
OneOf
. Ich habe den Eindruck, dass dies mitmatch
Anweisungen nicht gut funktioniert, Sie aber den Abgleich mit Funktionen höherer Ordnung simulieren können. Schauen Sie sich diesen Ausschnitt an zum Beispiel an, aber beachten Sie, dass der Teil "Simuliertes Matching" auskommentiert ist, vielleicht weil es noch nicht ganz funktioniert.Nun zu einer redaktionellen Bearbeitung: Ich glaube nicht, dass die Definition von Entweder3, Entweder4 usw., wie Sie sie beschreiben, etwas Ungeheuerliches ist. Dies ist im Wesentlichen doppelt so hoch wie die in Scala integrierten 22 Standardtupeltypen. Es wäre sicherlich schön, wenn Scala disjunktive Typen eingebaut hätte und vielleicht eine nette Syntax für sie wie
{x, y, z}
.quelle
Ich denke, dass der disjunkte Typ der ersten Klasse ein versiegelter Supertyp mit den alternativen Subtypen und impliziten Konvertierungen zu / von den gewünschten Typen der Disjunktion zu diesen alternativen Subtypen ist.
Ich gehe davon aus, dass dies die Kommentare 33 - 36 der Lösung von Miles Sabin anspricht , also den erstklassigen Typ, der am Verwendungsort verwendet werden kann, aber ich habe ihn nicht getestet.
Ein Problem ist, dass Scala bei übereinstimmendem Kontext keine implizite Konvertierung von
IntOfIntOrString
nachInt
(undStringOfIntOrString
nachString
) verwendet. Daher müssen Extraktoren definiert undcase Int(i)
stattdessen verwendet werdencase i : Int
.ADD: Ich habe Miles Sabin in seinem Blog wie folgt geantwortet. Vielleicht gibt es einige Verbesserungen gegenüber Entweder:
size(Left(2))
oder nichtsize(Right("test"))
.V
des Unionstyps besser, anstelle vonOr
z. B.IntVString
`Int |v| String
`, `Int or String
` oder meinem Favoriten `Int|String
` zu verwenden?UPDATE: Es folgt eine logische Negation der Disjunktion für das obige Muster, und ich habe ein alternatives (und wahrscheinlich nützlicheres) Muster in Miles Sabins Blog hinzugefügt .
EIN ANDERES UPDATE: In Bezug auf die Kommentare 23 und 35 der Lösung von Mile Sabin gibt es hier eine Möglichkeit, einen Vereinigungstyp am Verwendungsort zu deklarieren. Beachten Sie, dass es nach der ersten Ebene nicht mehr verpackt ist, dh es hat den Vorteil, dass es auf eine beliebige Anzahl von Typen in der Disjunktion erweiterbar ist , während
Either
verschachteltes Boxen erforderlich ist und das Paradigma in meinem vorherigen Kommentar 41 nicht erweiterbar war. Mit anderen Worten, a kann aD[Int ∨ String]
zugewiesen werden (dh ist ein Subtyp von) aD[Int ∨ String ∨ Double]
.Anscheinend hat der Scala-Compiler drei Fehler.
D[¬[Double]]
vom Spiel ausgeschlossen.3.
Die get-Methode ist für den Eingabetyp nicht richtig eingeschränkt, da der Compiler
A
die kovariante Position nicht zulässt . Man könnte argumentieren, dass dies ein Fehler ist, da wir nur Beweise wollen und niemals auf die Beweise in der Funktion zugreifen. Und ich habe die Entscheidung getroffen, nichtcase _
in derget
Methode zu testen , damit ich keineOption
in dermatch
In entpacken musssize()
.05. März 2012: Das vorherige Update muss verbessert werden. Die Lösung von Miles Sabin funktionierte korrekt mit Subtypisierung.
Der Vorschlag meines vorherigen Updates (für einen nahezu erstklassigen Gewerkschaftstyp) hat die Untertypisierung unterbrochen.
Das Problem ist, dass
A
in(() => A) => A
sowohl in der Kovariante (Rückgabetyp) als auch in der Kontravariante (Funktionseingabe oder in diesem Fall ein Rückgabewert der Funktion, die eine Funktionseingabe ist) auftritt, sodass Substitutionen nur invariant sein können.Beachten Sie, dass
A => Nothing
dies nur notwendig ist, weil wirA
in der kontravarianten Position wollen, damit Supertypen vonA
weder Subtypen vonD[¬[A]]
noch sindD[¬[A] with ¬[U]]
( siehe auch ). Da wir nur eine doppelte Kontravarianz benötigen, können wir das Äquivalent zur Miles-Lösung erreichen, selbst wenn wir das¬
und verwerfen können∨
.Das komplette Update ist also.
Beachten Sie, dass die vorherigen 2 Fehler in Scala bestehen bleiben, der dritte jedoch vermieden wird, da er
T
nun auf den Subtyp beschränkt istA
.Wir können die Untertypisierung bestätigen.
Ich habe nur gedacht , dass erstklassige Schnittarten sind sehr wichtig, sowohl für die Gründe Ceylon sie hat , und weil statt subsumieren zu ,
Any
welche Mittel mit einem Unboxingmatch
auf erwarteten Typen können einen Laufzeitfehler erzeugen, die Unboxing eines ( heterogene Sammlung enthalten a) Die Disjunktion kann typgeprüft werden (Scala muss die von mir festgestellten Fehler beheben). Gewerkschaften sind einfacher als die Komplexität der Verwendung der experimentellen HList von Metascala für heterogene Sammlungen.quelle
size
Funktion vermieden werden .size
nicht mehrD[Any]
als Eingabe akzeptiert wird .Es gibt einen anderen Weg, der etwas einfacher zu verstehen ist, wenn Sie Curry-Howard nicht groken:
Ich benutze ähnliche Techniken in Dijon
quelle
Nun, das ist alles sehr klug, aber ich bin mir ziemlich sicher, dass Sie bereits wissen, dass die Antworten auf Ihre Leitfragen verschiedene Arten von "Nein" sind. Scala geht anders mit Überladung um und muss zugegebenermaßen etwas weniger elegant sein, als Sie beschreiben. Ein Teil davon ist auf die Java-Interoperabilität zurückzuführen, ein anderer Teil darauf, dass nicht kantige Fälle des Typ-Inferenz-Algorithmus getroffen werden sollen, und ein anderer Teil darauf, dass es einfach nicht Haskell ist.
quelle
Hinzu kommen die bereits tollen Antworten hier. Hier ist ein Kern, der auf Miles Sabin-Unionstypen (und Joshs Ideen) aufbaut, sie aber auch rekursiv definiert, sodass Sie> 2 Typen in der Union haben können (
def foo[A : UNil Or Int Or String Or List[String]
)https://gist.github.com/aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb
NB: Ich sollte hinzufügen, dass ich, nachdem ich für ein Projekt mit den oben genannten Dingen herumgespielt habe, zu einfachen alten Summentypen zurückgekehrt bin (dh zu einem versiegelten Merkmal mit Unterklassen). Miles Sabin-Unionstypen eignen sich hervorragend zum Einschränken des Typparameters. Wenn Sie jedoch einen Unionstyp zurückgeben müssen, bietet er nicht viel.
quelle
A|C <: A|B|C
Subtypisierungsproblem lösen? stackoverflow.com/questions/45255270/… Mein Bauchgefühl NEIN, denn dann würde es bedeuten,A or C
dass dies der Subtyp von sein müsste, der(A or B) or C
aber nicht den Typ enthält,A or C
so dass es keine Hoffnung gibt , zumindest mit dieser CodierungA or C
einen SubtypA or B or C
daraus zu machen. . Was denken Sie ?Aus den Dokumenten mit dem Zusatz von
sealed
:Zum
sealed
Teil:quelle