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.
sealed trait IntOrString
case class IntOfIntOrString( v:Int ) extends IntOrString
case class StringOfIntOrString( v:String ) extends IntOrString
implicit def IntToIntOfIntOrString( v:Int ) = new IntOfIntOrString(v)
implicit def StringToStringOfIntOrString( v:String ) = new StringOfIntOrString(v)
object Int {
def unapply( t : IntOrString ) : Option[Int] = t match {
case v : IntOfIntOrString => Some( v.v )
case _ => None
}
}
object String {
def unapply( t : IntOrString ) : Option[String] = t match {
case v : StringOfIntOrString => Some( v.v )
case _ => None
}
}
def size( t : IntOrString ) = t match {
case Int(i) => i
case String(s) => s.length
}
scala> size("test")
res0: Int = 4
scala> size(2)
res1: Int = 2
Ein Problem ist, dass Scala bei übereinstimmendem Kontext keine implizite Konvertierung von IntOfIntOrString
nach Int
(und StringOfIntOrString
nach String
) verwendet. Daher müssen Extraktoren definiert und case Int(i)
stattdessen verwendet werden case i : Int
.
ADD: Ich habe Miles Sabin in seinem Blog wie folgt geantwortet. Vielleicht gibt es einige Verbesserungen gegenüber Entweder:
- Es erstreckt sich auf mehr als zwei Typen, ohne zusätzliches Rauschen an der Verwendungs- oder Definitionsstelle.
- Argumente sind implizit eingerahmt, zB brauchen
size(Left(2))
oder nicht size(Right("test"))
.
- Die Syntax des Mustervergleichs ist implizit entpackt.
- Das Boxen und Entpacken kann durch den JVM-Hotspot optimiert werden.
- Die Syntax könnte die eines zukünftigen erstklassigen Unionstyps sein, sodass die Migration möglicherweise nahtlos sein könnte. Vielleicht wäre es für den Namen
V
des Unionstyps besser, anstelle von Or
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 .
sealed trait `Int or String`
sealed trait `not an Int or String`
sealed trait `Int|String`[T,E]
case class `IntOf(Int|String)`( v:Int ) extends `Int|String`[Int,`Int or String`]
case class `StringOf(Int|String)`( v:String ) extends `Int|String`[String,`Int or String`]
case class `NotAn(Int|String)`[T]( v:T ) extends `Int|String`[T,`not an Int or String`]
implicit def `IntTo(IntOf(Int|String))`( v:Int ) = new `IntOf(Int|String)`(v)
implicit def `StringTo(StringOf(Int|String))`( v:String ) = new `StringOf(Int|String)`(v)
implicit def `AnyTo(NotAn(Int|String))`[T]( v:T ) = new `NotAn(Int|String)`[T](v)
def disjunction[T,E](x: `Int|String`[T,E])(implicit ev: E =:= `Int or String`) = x
def negationOfDisjunction[T,E](x: `Int|String`[T,E])(implicit ev: E =:= `not an Int or String`) = x
scala> disjunction(5)
res0: Int|String[Int,Int or String] = IntOf(Int|String)(5)
scala> disjunction("")
res1: Int|String[String,Int or String] = StringOf(Int|String)()
scala> disjunction(5.0)
error: could not find implicit value for parameter ev: =:=[not an Int or String,Int or String]
disjunction(5.0)
^
scala> negationOfDisjunction(5)
error: could not find implicit value for parameter ev: =:=[Int or String,not an Int or String]
negationOfDisjunction(5)
^
scala> negationOfDisjunction("")
error: could not find implicit value for parameter ev: =:=[Int or String,not an Int or String]
negationOfDisjunction("")
^
scala> negationOfDisjunction(5.0)
res5: Int|String[Double,not an Int or String] = NotAn(Int|String)(5.0)
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 a D[Int ∨ String]
zugewiesen werden (dh ist ein Subtyp von) a D[Int ∨ String ∨ Double]
.
type ¬[A] = (() => A) => A
type ∨[T, U] = ¬[T] with ¬[U]
class D[-A](v: A) {
def get[T](f: (() => T)) = v match {
case x : ¬[T] => x(f)
}
}
def size(t: D[Int ∨ String]) = t match {
case x: D[¬[Int]] => x.get( () => 0 )
case x: D[¬[String]] => x.get( () => "" )
case x: D[¬[Double]] => x.get( () => 0.0 )
}
implicit def neg[A](x: A) = new D[¬[A]]( (f: (() => A)) => x )
scala> size(5)
res0: Any = 5
scala> size("")
error: type mismatch;
found : java.lang.String("")
required: D[?[Int,String]]
size("")
^
scala> size("hi" : D[¬[String]])
res2: Any = hi
scala> size(5.0 : D[¬[Double]])
error: type mismatch;
found : D[(() => Double) => Double]
required: D[?[Int,String]]
size(5.0 : D[?[Double]])
^
Anscheinend hat der Scala-Compiler drei Fehler.
- Nach dem ersten Typ in der Zieldisjunktion wird für keinen Typ die richtige implizite Funktion ausgewählt.
- Der Fall wird nicht
D[¬[Double]]
vom Spiel ausgeschlossen.
3.
scala> class D[-A](v: A) {
def get[T](f: (() => T))(implicit e: A <:< ¬[T]) = v match {
case x : ¬[T] => x(f)
}
}
error: contravariant type A occurs in covariant position in
type <:<[A,(() => T) => T] of value e
def get[T](f: (() => T))(implicit e: A <:< ?[T]) = v match {
^
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, nicht case _
in der get
Methode zu testen , damit ich keine Option
in der match
In entpacken muss size()
.
05. März 2012: Das vorherige Update muss verbessert werden. Die Lösung von Miles Sabin funktionierte korrekt mit Subtypisierung.
type ¬[A] = A => Nothing
type ∨[T, U] = ¬[T] with ¬[U]
class Super
class Sub extends Super
scala> implicitly[(Super ∨ String) <:< ¬[Super]]
res0: <:<[?[Super,String],(Super) => Nothing] =
scala> implicitly[(Super ∨ String) <:< ¬[Sub]]
res2: <:<[?[Super,String],(Sub) => Nothing] =
scala> implicitly[(Super ∨ String) <:< ¬[Any]]
error: could not find implicit value for parameter
e: <:<[?[Super,String],(Any) => Nothing]
implicitly[(Super ? String) <:< ?[Any]]
^
Der Vorschlag meines vorherigen Updates (für einen nahezu erstklassigen Gewerkschaftstyp) hat die Untertypisierung unterbrochen.
scala> implicitly[D[¬[Sub]] <:< D[(Super ∨ String)]]
error: could not find implicit value for parameter
e: <:<[D[(() => Sub) => Sub],D[?[Super,String]]]
implicitly[D[?[Sub]] <:< D[(Super ? String)]]
^
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 wir A
in der kontravarianten Position wollen, damit Supertypen von A
weder Subtypen von D[¬[A]]
noch sind D[¬[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 ∨
.
trait D[-A]
scala> implicitly[D[D[Super]] <:< D[D[Super] with D[String]]]
res0: <:<[D[D[Super]],D[D[Super] with D[String]]] =
scala> implicitly[D[D[Sub]] <:< D[D[Super] with D[String]]]
res1: <:<[D[D[Sub]],D[D[Super] with D[String]]] =
scala> implicitly[D[D[Any]] <:< D[D[Super] with D[String]]]
error: could not find implicit value for parameter
e: <:<[D[D[Any]],D[D[Super] with D[String]]]
implicitly[D[D[Any]] <:< D[D[Super] with D[String]]]
^
Das komplette Update ist also.
class D[-A] (v: A) {
def get[T <: A] = v match {
case x: T => x
}
}
implicit def neg[A](x: A) = new D[D[A]]( new D[A](x) )
def size(t: D[D[Int] with D[String]]) = t match {
case x: D[D[Int]] => x.get[D[Int]].get[Int]
case x: D[D[String]] => x.get[D[String]].get[String]
case x: D[D[Double]] => x.get[D[Double]].get[Double]
}
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 ist A
.
Wir können die Untertypisierung bestätigen.
def size(t: D[D[Super] with D[String]]) = t match {
case x: D[D[Super]] => x.get[D[Super]].get[Super]
case x: D[D[String]] => x.get[D[String]].get[String]
}
scala> size( new Super )
res7: Any = Super@1272e52
scala> size( new Sub )
res8: Any = Sub@1d941d7
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 Unboxing match
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.
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
.