Wie definiert man “Typ Disjunktion” (Union-Typen)?

Ein Weg, der vorgeschlagen wurde , um mit Doppeldefinitionen von überladenen Methoden umzugehen, besteht darin, das Überladen durch Musterübereinstimmung zu ersetzen:

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 Überprüfung statischer Typen auf die Argumente für foo aufgeben. 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 mit beiden nah kommen, aber es wird mit mehr als zwei Arten hässlich schnell:

 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 so aus, als ob eine allgemeine (elegante, effiziente) Lösung Either3 , Either4 , … definieren Either3 . Kennt jemand eine alternative Lösung, um das gleiche Ziel zu erreichen? Soweit ich weiß, hat Scala keine eingebaute “Typ-Disjunktion”. Liegen die oben definierten impliziten Konvertierungen auch irgendwo in der Standardbibliothek, so dass ich sie einfach importieren kann?

Nun, im speziellen Fall von Any* wird dieser Trick unten nicht funktionieren, da er gemischte Typen nicht akzeptiert. Da gemischte Typen jedoch auch nicht mit Überladung arbeiten, könnte dies das sein, was Sie wollen.

Deklarieren Sie zuerst eine class mit den Typen, die Sie akzeptieren möchten:

 class StringOrInt[T] object StringOrInt { implicit object IntWitness extends StringOrInt[Int] implicit object StringWitness extends StringOrInt[String] } 

Als nächstes deklariere foo so:

 object Bar { def foo[T: StringOrInt](x: T) = x match { case _: String => println("str") case _: Int => println("int") } } 

Und das ist es. Sie können foo(5) oder foo("abc") aufrufen, und es wird funktionieren, aber versuchen Sie foo(true) und es wird scheitern. Dies könnte durch den Client-Code durch Erstellen eines StringOrInt[Boolean] , es sei denn, wie unten von Randall angemerkt, machen Sie StringOrInt einer sealed class.

Es funktioniert, weil T: StringOrInt bedeutet, dass es einen impliziten Parameter vom Typ StringOrInt[T] , und weil Scala in Companion-Objekten eines Typs sucht, um zu sehen, ob es dort Implicits gibt, um Code für diesen Typ arbeiten zu lassen.

Miles Sabin beschreibt eine sehr nette Möglichkeit, in seinem letzten Blog Post Unboxed union-Typen in Scala über den Curry-Howard-Isomorphismus den Union-Typ zu erhalten:

Er definiert zuerst die Negation von Typen als

 type ¬[A] = A => Nothing 

Indem er De Morgans Gesetz verwendet, erlaubt es ihm, Unionstypen zu definieren

 type ∨[T, U] = ¬[¬[T] with ¬[U]] 

Mit den folgenden Hilfskonstrukten

 type ¬¬[A] = ¬[¬[A]] type |∨|[T, U] = { type λ[X] = ¬¬[X] <: < (T ∨ U) } 

Sie können Union-Typen wie folgt schreiben:

 def size[T : (Int |∨| String)#λ](t : T) = t match { case i : Int => i case s : String => s.length } 

Dotty , ein neuer experimenteller Scala-Compiler, unterstützt Union-Typen (geschrieben A | B ), damit Sie genau das tun können, was Sie wollten:

 def foo(xs: (String | Int)*) = xs foreach { case _: String => println("str") case _: Int => println("int") } 

Hier ist der Rex Kerr Weg, Unionstypen zu kodieren. Gerade und einfach!

 scala> def f[A](a: A)(implicit ev: (Int with String) <: < A) = a match { | case i: Int => i + 1 | case s: String => s.length | } f: [A](a: A)(implicit ev: <: <[Int with String,A])Int scala> f(3) res0: Int = 4 scala> f("hello") res1: Int = 5 scala> f(9.2) :9: error: Cannot prove that Int with String <: < Double. f(9.2) ^ 

Quelle: Kommentar # 27 unter diesem ausgezeichneten Blogbeitrag von Miles Sabin, der eine andere Möglichkeit zur Kodierung von Union-Typen in Scala bietet.

Es ist möglich, Daniels Lösung wie folgt zu verallgemeinern:

 sealed trait Or[A, B] object Or { implicit def a2Or[A,B](a: A) = new Or[A, B] {} implicit def b2Or[A,B](b: B) = new Or[A, B] {} } object Bar { def foo[T < % String Or Int](x: T) = x match { case _: String => println("str") case _: Int => println("int") } } 

Die Hauptnachteile dieses Ansatzes sind

  • Wie Daniel herausfand, behandelt es keine Collections / Varargs mit gemischten Typen
  • Der Compiler gibt keine Warnung aus, wenn die Übereinstimmung nicht erschöpfend ist
  • Der Compiler gibt keinen Fehler aus, wenn die Übereinstimmung einen unmöglichen Fall enthält
  • Wie bei Either Or3 Ansätze würde eine weitere Verallgemeinerung die Definition analoger Or3 , Or4 usw. Merkmale erfordern. Natürlich wäre die Definition solcher Merkmale viel einfacher als die Definition der entsprechenden classn.

Aktualisieren:

Mitch Blevins zeigt eine sehr ähnliche Herangehensweise und zeigt, wie man es auf mehr als zwei Arten verallgemeinert und es als “Stottern” bezeichnet.

Ich bin über eine relativ saubere Implementierung von n-stufigen Unionstypen gestolpert, 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.

Vorgegebener Typ ¬[-A] der kontravariant auf A , definitionsgemäß gegeben A <: B wir schreiben ¬[B] <: ¬[A] , die Reihenfolge der Typen invertierend.

Bei den Typen A , B und X wollen wir X <: A || X <: B express X <: A || X <: B Wenn wir die Kontravarianz anwenden, erhalten wir ¬[A] <: ¬[X] || ¬[B] <: ¬[X] ¬[A] <: ¬[X] || ¬[B] <: ¬[X] . Dies kann wiederum ausgedrückt werden als ¬[A] with ¬[B] <: ¬[X] wobei einer von A oder B ein Supertyp von X oder X selbst sein muss (denke an functionsargumente).

 object Union { import scala.language.higherKinds sealed trait ¬[-A] sealed trait TSet { type Compound[A] type Map[F[_]] <: TSet } sealed trait ∅ extends TSet { type Compound[A] = A type Map[F[_]] = ∅ } // Note that this type is left-associative for the sake of concision. sealed trait ∨[T <: TSet, H] extends TSet { // Given a type of the form `∅ ∨ A ∨ B ∨ ...` and parameter `X`, we want to produce the type // `¬[A] with ¬[B] with ... <:< ¬[X]`. type Member[X] = T#Map[¬]#Compound[¬[H]] <:< ¬[X] // This could be generalized as a fold, but for concision we leave it as is. type Compound[A] = T#Compound[H with A] type Map[F[_]] = T#Map[F] ∨ F[H] } def foo[A : (∅ ∨ String ∨ Int ∨ List[Int])#Member](a: A): String = a match { case s: String => "String" case i: Int => "Int" case l: List[_] => "List[Int]" } foo(42) foo("bar") foo(List(1, 2, 3)) foo(42d) // error foo[Any](???) // error } 

Ich habe einige Zeit damit verbracht, diese Idee mit einer Obergrenze für Membertypen zu kombinieren, wie es in den TList von Harrah / Up zu sehen ist , aber die Implementierung von Map mit Typgrenzen hat sich bisher als schwierig erwiesen.

Eine Typklassenlösung ist wahrscheinlich der beste Weg, hier zu gehen und Implicits zu verwenden. Dies ähnelt dem im Buch Odersky / Spoon / Venners erwähnten Monoidansatz:

 abstract class NameOf[T] { def get : String } implicit object NameOfStr extends NameOf[String] { def get = "str" } implicit object NameOfInt extends NameOf[Int] { def get = "int" } def printNameOf[T](t:T)(implicit name : NameOf[T]) = println(name.get) 

Wenn Sie dies dann in der REPL ausführen:

 scala> printNameOf(1) int scala> printNameOf("sss") str scala> printNameOf(2.0f) :10: error: could not find implicit value for parameter nameOf: NameOf[ Float] printNameOf(2.0f) ^ 

Es gibt auch diesen Hack :

 implicit val x: Int = 0 def foo(a: List[Int])(implicit ignore: Int) { } implicit val y = "" def foo(a: List[String])(implicit ignore: String) { } foo(1::2::Nil) foo("a"::"b"::Nil) 

Siehe Umgehen von Typ-Lösch-Ambiguitäten (Scala) .

Wir hätten gern einen Typoperator Or[U,V] , mit dem ein Typparameter X so eingeschränkt werden kann, dass entweder X <: U oder X <: V . Hier ist eine Definition, die so nah wie möglich kommt:

 trait Inv[-X] type Or[U,T] = { type pf[X] = (Inv[U] with Inv[T]) <: < Inv[X] } 

Hier ist, wie es benutzt wird:

 // use class A; class B extends A; class C extends B def foo[X : (B Or String)#pf] = {} foo[B] // OK foo[C] // OK foo[String] // OK foo[A] // ERROR! foo[Number] // ERROR! 

Dies verwendet ein paar Tricks vom Typ Scala. Die wichtigste ist die Verwendung von generalisierten Typ-Constraints . Bei den Typen U und V liefert der Scala-Compiler eine class namens U <: < V (und ein implizites Objekt dieser class) genau dann, wenn der Scala-Compiler beweisen kann, dass U ein Subtyp von V . Hier ist ein einfacheres Beispiel mit generalisierten Typ-Constraints, die in einigen Fällen funktionieren:

 def foo[X](implicit ev : (B with String) <: < X) = {} 

Dieses Beispiel funktioniert, wenn X eine Instanz der class B , ein String oder ein Typ ist, der weder ein Supertyp noch ein Subtyp von B oder String . In den ersten beiden Fällen stimmt es mit der Definition des with Schlüsselwortes überein, dass (B with String) <: B und (B with String) <: String , also Scala ein implizites Objekt liefert, das als ev : das Der Scala-Compiler akzeptiert foo[B] und foo[String] korrekt.

Im letzten Fall verlasse ich mich auf die Tatsache, dass, wenn U with V <: X , dann U <: X oder V <: X Es scheint intuitiv wahr zu sein, und ich nehme es einfach an. Aus dieser Annahme ergibt sich, warum dieses einfache Beispiel fehlschlägt, wenn X ein Supertyp oder Subtyp von B oder String : Beispiel: Im obigen Beispiel wird foo[A] fälschlicherweise akzeptiert und foo[C] wird fälschlicherweise zurückgewiesen. Was wir wollen, ist wiederum eine Art Ausdruck für die Variablen U , V und X , die genau dann wahr ist, wenn X <: U oder X <: V .

Scalas Begriff der Kontravarianz kann hier helfen. trait Inv[-X] du dich an das Merkmalsmerkmal trait Inv[-X] ? Weil es in seinem Typparameter X , Inv[X] <: Inv[Y] kontravariant ist, wenn Y <: X Das bedeutet, dass wir das obige Beispiel durch eines ersetzen können, das tatsächlich funktioniert:

 trait Inv[-X] def foo[X](implicit ev : (Inv[B] with Inv[String]) <: < Inv[X]) = {} 

Der Grund dafür ist, dass der Ausdruck (Inv[U] with Inv[V]) <: Inv[X] nach der gleichen obigen Annahme wahr ist, wenn Inv[U] <: Inv[X] oder Inv[V] <: Inv[X] , und durch die Definition der Kontravarianz ist dies genau richtig, wenn X <: U oder X <: V

Es ist möglich, die Dinge ein wenig wiederverwendbarer zu machen, indem man einen parametrisierbaren Typ BOrString[X] deklariert und wie folgt verwendet:

 trait Inv[-X] type BOrString[X] = (Inv[B] with Inv[String]) <: < Inv[X] def foo[X](implicit ev : BOrString[X]) = {} 

Scala wird nun versuchen, für jedes X , mit dem foo aufgerufen wird, den Typ BOrString[X] zu konstruieren, und der Typ wird genau dann konstruiert, wenn X ein Subtyp von B oder String . Das funktioniert, und es gibt eine Kurzschreibweise. Die folgende Syntax ist äquivalent (mit der Ausnahme, dass ev jetzt im Methodenrumpf als implicitly[BOrString[X]] anstelle von ev ) BOrString und BOrString als BOrString verwendet:

 def foo[X : BOrString] = {} 

Was wir wirklich gerne hätten, ist eine flexible Möglichkeit, einen Typkontext zu erstellen. Ein Typkontext muss ein parametrisierbarer Typ sein, und wir möchten einen parametrisierbaren Weg, um einen Typkontext zu erstellen. Das klingt, als ob wir versuchen, functionen auf Typen zu curren, genau wie wir functionen auf Werten curry. Mit anderen Worten, wir möchten etwas wie das Folgende:

 type Or[U,T][X] = (Inv[U] with Inv[T]) <: < Inv[X] 

Das ist in Scala nicht direkt möglich , aber es gibt einen Trick, mit dem wir uns näher kommen können. Das bringt uns zur Definition von Or oben:

 trait Inv[-X] type Or[U,T] = { type pf[X] = (Inv[U] with Inv[T]) <: < Inv[X] } 

Hier verwenden wir strukturelle Typisierung und Scalas Pfundoperator , um einen Strukturtyp Or[U,T] zu erzeugen, der garantiert einen internen Typ hat. Dies ist eine seltsame Bestie. Um einen Kontext zu geben, muss die function def bar[X <: { type Y = Int }](x : X) = {} mit Unterklassen von AnyRef , für die ein Typ Y definiert ist:

 bar(new AnyRef{ type Y = Int }) // works! 

Mit dem Operator pound können wir uns auf den inneren Typ Or[B, String]#pf und die Infix-Notation für den Typ Operator Or , um zu unserer ursprünglichen Definition von foo :

 def foo[X : (B Or String)#pf] = {} 

Wir können die Tatsache verwenden, dass functionstypen in ihrem ersten Typparameter kontravariant sind, um zu vermeiden, das Merkmal Inv :

 type Or[U,T] = { type pf[X] = ((U => _) with (T => _)) <: < (X => _) } 

Sie können sich MetaScala anschauen , die etwas namens OneOf . Ich habe den Eindruck, dass dies mit match Statements nicht gut funktioniert, Sie aber Matching mit functionen höherer Ordnung simulieren können. Schauen Sie sich zum Beispiel dieses Snippet an, aber beachten Sie, dass der “simulated matching” -Teil auskommentiert ist, vielleicht weil es noch nicht ganz funktioniert.

Nun zu einigen redaktionellen Arbeiten: Ich denke nicht, dass es etwas Unerfreuliches gibt, Entweder3, Entweder4 usw. zu definieren, wie du es beschreibst. Dies ist im Wesentlichen doppelt zu den Standard 22 Tupeltypen, die in Scala eingebaut sind. Es wäre sicherlich nett, wenn Scala disjunktive Typen und vielleicht eine nette Syntax für sie hätte, wie {x, y, z} .

Ich denke, dass der disjunkteste Typ der ersten class ein versiegelter Supertyp mit den alternativen Subtypen und impliziten Konvertierungen zu / von den gewünschten Typen der Disjunktion zu diesen alternativen Subtypen ist.

Ich nehme an, dass dies die Kommentare 33 – 36 von Miles Sabins Lösung betrifft, also der erste classntyp, der an der Verwendungsstelle 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( vv ) case _ => None } } object String { def unapply( t : IntOrString ) : Option[String] = t match { case v : StringOfIntOrString => Some( vv ) 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 keine implizite Konvertierung von IntOfIntOrString zu Int (und StringOfIntOrString zu String ) verwendet, also Extraktoren definieren und case Int(i) anstelle von case i : Int .


ADD: Ich habe Miles Sabin in seinem Blog wie folgt geantwortet. Vielleicht gibt es mehrere Verbesserungen gegenüber Entweder:

  1. Es erstreckt sich auf mehr als 2 Typen, ohne zusätzliches Rauschen an der Verwendungs ​​oder Definitionsseite.
  2. Argumente werden implizit eingerahmt, zB brauchen Sie keine size(Left(2)) oder size(Right("test")) .
  3. Die Syntax des Mustervergleichs wird implizit aufgehoben.
  4. Das Boxen und das Unboxing können vom JVM-Hotspot weg optimiert werden.
  5. Die Syntax könnte die sein, die von einem zukünftigen Unionstyp erster class übernommen wird, sodass die Migration vielleicht nahtlos verläuft? Vielleicht wäre es für den Namen der Vereinigungsart besser, V anstelle von Or , zB IntVString , Int |v| String Int |v| String `,` Int or String `oder mein Lieblings` Int|String `?

UPDATE: Logische Negation der Disjunktion für das obige Muster folgt, und ich fügte ein alternatives (und wahrscheinlich nützlicheres) Muster in Miles Sabins Blog hinzu .

 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 von Mile Sabins Lösung , hier ist eine Möglichkeit, einen Unionstyp auf der Verwendungsseite zu deklarieren. Beachten Sie, dass es nach der ersten Ebene ungepackt ist, dh es hat den Vorteil, dass es auf eine beliebige Anzahl von Typen in der Disjunktion erweiterbar ist , während Either verschachteltes Boxen benötigt und das Paradigma in meinem vorherigen Kommentar 41 nicht erweiterbar war. Mit anderen Worten, ein D[Int ∨ String] ist zuweisbar (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 Bugs.

  1. Es wird nicht die richtige implizite function für irgendeinen Typ nach dem ersten Typ in der Ziel-Disjunktion auswählen.
  2. Es schließt den Fall D[¬[Double]] nicht aus dem Spiel aus.

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 Methode "get" ist beim Eingabetyp nicht korrekt eingeschränkt, da der Compiler A in der kovarianten Position nicht zulässt. Man könnte argumentieren, dass dies ein Fehler ist, weil alles, was wir wollen, Beweis ist, dass wir niemals auf die Beweise in der function zugreifen. Und ich habe die Wahl getroffen, in der get Methode nicht nach case _ zu testen, so dass ich keine Option in der match in size() auspacken müsste.


5. März 2012: Das vorherige Update benötigt eine Verbesserung. Miles Sabins Lösung funktionierte korrekt mit Subtyping.

 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 den ersten Verbindungstyp erster Art) hat die Subtypisierung abgebrochen.

  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 kovarianten (Rückgabetyp) als auch in der kontravarianten (functionseingabe oder in diesem Fall ein Rückgabewert der function, die eine functionseingabe ist) Positionen, also Substitutionen, darstellt kann nur invariant sein.

Man beachte, dass A => Nothing ist nur notwendig, weil wir A in der kontravarianten Position haben wollen, so dass Supertypen von A keine Subtypen von D[¬[A]] noch D[¬[A] with ¬[U]] ( siehe auch ). Da wir nur eine doppelte Kontravarianz benötigen, können wir die Lösung von Miles erreichen, auch wenn wir das ¬ und vercasting 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]]] ^ 

Also die komplette Lösung ist.

 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 Bugs in Scala erhalten bleiben, aber der dritte wird vermieden, da T nun als Subtyp von A .

Wir können die Subtyping-Arbeiten 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 gedacht, dass erstklassige Kreuzungsarten sehr wichtig sind, sowohl aus den Gründen, die Ceylon hat , als auch, weil sie statt Any subsumieren können , was bedeutet, dass Unboxing mit einer match auf erwarteten Typen einen Laufzeiterrors erzeugen kann Sammlung mit a) Disjunktion kann Typ überprüft werden (Scala muss die Fehler beheben, die ich notierte). Gewerkschaften sind einfacher als die Komplexität der Verwendung der experimentellen HList von Metascala für heterogene Sammlungen.

Es gibt einen anderen Weg, der etwas leichter zu verstehen ist, wenn Sie nicht Curry-Howard:

 type v[A,B] = Either[Option[A], Option[B]] private def L[A,B](a: A): v[A,B] = Left(Some(a)) private def R[A,B](b: B): v[A,B] = Right(Some(b)) // TODO: for more use scala macro to generate this for up to 22 types? implicit def a2[A,B](a: A): v[A,B] = L(a) implicit def b2[A,B](b: B): v[A,B] = R(b) implicit def a3[A,B,C](a: A): v[v[A,B],C] = L(a2(a)) implicit def b3[A,B,C](b: B): v[v[A,B],C] = L(b2(b)) implicit def a4[A,B,C,D](a: A): v[v[v[A,B],C],D] = L(a3(a)) implicit def b4[A,B,C,D](b: B): v[v[v[A,B],C],D] = L(b3(b)) implicit def a5[A,B,C,D,E](a: A): v[v[v[v[A,B],C],D],E] = L(a4(a)) implicit def b5[A,B,C,D,E](b: B): v[v[v[v[A,B],C],D],E] = L(b4(b)) type JsonPrimtives = (String v Int v Double) type ValidJsonPrimitive[A] = A => JsonPrimtives def test[A : ValidJsonPrimitive](x: A): A = x test("hi") test(9) // test(true) // does not compile 

Ich benutze ähnliche Technik in Dijon

Nun, das ist alles sehr schlau, aber ich bin mir ziemlich sicher, dass Sie bereits wissen, dass die Antworten auf Ihre Leitfragen verschiedene Varianten von “Nein” sind. Scala behandelt Überladung anders und, zugegebenermaßen, etwas weniger elegant, als Sie beschreiben. Einige davon sind auf die Interoperabilität mit Java zurückzuführen, einige davon liegen darin, dass sie keine kantigen Fälle vom Typ-Inferenz-Algorithmus erreichen wollen, und einige davon sind einfach dadurch, dass sie nicht Haskell sind.

Hinzufügen zu den bereits tollen Antworten hier. Hier ist ein core, der auf Miles Sabin Unionstypen (und Joshs Ideen) aufbaut, sie aber auch rekursiv definiert, so dass Sie in der Union> 2 Typen 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, nachdem ich mit dem oben genannten für ein Projekt herumgespielt habe, ich zurück zu einfachen alten Summen-Typen ging (dh versiegelte Eigenschaft mit Unterklassen). Miles Sabin union types are great for restricting the type parameter, but if you need to return a union type then it doesn’t offer much.

From the docs , with the addition of sealed :

 sealed class Expr case class Var (x: String) extends Expr case class Apply (f: Expr, e: Expr) extends Expr case class Lambda(x: String, e: Expr) extends Expr 

Regarding the sealed part:

It is possible to define further case classes that extend type Expr in other parts of the program (…). This form of extensibility can be excluded by declaring the base class Expr sealed; in this case, all classes that directly extend Expr must be in the same source file as Expr.