2017-01-24 3 views
4

[EDITED BELOW]Abgeleitete Klassen als Klassen Konstruktorparameter

Ich habe eine Klassenhierarchie, die, gerade jetzt ist, etwa wie folgt:

sealed trait Foo { 
    def method: Any 
} 

case class Bar extends Foo { 
    def method: Array[String] = // implementation 
} 

case class Baz extends Foo { 
    def method: Map[String, Array[String]] = // implementation 
} 

Ich bin die abstrakte Methode einen Rückgabetyp von Any geben weil die Rückgabetypen der Fallklassen notwendigerweise unterschiedlich sind, aber sie haben einen ähnlichen Zweck. Aus diesem Grund möchte ich es behalten, dieses übliche Verhalten zu modellieren, und dies ist die einzige Möglichkeit, die ich gefunden habe, um es kompilieren zu lassen. Mir ist klar, dass dies gegen den Geist von Scalas Typsystem ist, also stelle ich die erste Frage unten.

Dann erwartet eine weitere Klasse eine Unterklasse von Foo als Konstrukteur Parameter, und ich weiß nicht, wie dies zu bezeichnen, andere als die folgenden:

class Qux(foo: Foo) { 
    val m = foo.method 
    ... 
    ... 
} 

Später in der Klasse Qux es Methoden gibt, die erwarten die val m des Typs an die jeweiligen Unterklasse (Bar oder Baz) von Foo entsprechend zu sein, aber ich bin immer Kompilierungsfehlern wie

... type mismatch; 
[error] found : Any 
[error] required: Array[String] 

Also habe ich ein paar Fragen:

  • ich mit Scala vertraut genug bin zu glauben, dass dies der richtige Weg ist mein besonderes Problem darstellen, aber nicht vertraut genug mit ihm zu wissen, wie man das macht. Was ist der richtige Weg, um das zu tun, was ich versuche?
  • Auch ist es eine Möglichkeit, Klasse sagen Qux dass m sollte durch die spezifische method von Bar oder Baz und nicht die abstrakte Methode von Foo zurück als Wert behandelt werden?

Edit: den Ansatz von @marios vorgeschlagen Taking (abstrakte Elemente vom Typ verwendet wird) scheint ein Schritt in die richtige Richtung zu sein, aber ein Typenkonflikt erscheint jetzt auf. Innerhalb der Klasse Qux, ich habe jetzt

class Qux[X <: Foo](sc: SparkContext, val foo: X) { 
    val m: foo.A = foo.method 

    def process(rows: DataFrame) = foo match { 
    case Bar(sc, _) => BarProcessor(sc, m).doStuff(rows) 
    case Baz(sc, _) => BazProcessor(sc, m.keys).doStuff(rows, m.values) 
    } 
} 

Wo BarProcessor mit instanziiert wird zum Beispiel eine Array[String] und BazProcessor braucht, um die Schlüssel-Wert-Paare aus dem Rückgabewert von Baz ‚s method Sachen zu tun. Allerdings bin ich jetzt Fehler wie

[error] Qux.scala:4: type mismatch; 
[error] found : Qux.this.foo.A 
[error] required: Array[String] 
[error]  case Bar(sc, _) => BarProcessor(sc, m).doStuff(rows) 
[error]          ^

Ähnliche Fehler zeigen sich immer, wenn ich versuche Map -spezifische Methoden auf m zu rufen, wenn foo ist ein Baz (entlang der Linien von value keys is not a member of Qux.this.foo.A, etc.). Ich verstehe, dass m ist nicht wirklich ein Array[String] - es ist vom Typ A. Aber gibt es eine Möglichkeit, Scala zu sagen, es in seinen gewünschten Typ zu übersetzen?

+1

"Typ-Parameter", die durch eine "gebunden" eingeschränkt werden kann. –

Antwort

4

Ein einfacher Weg, um die einzelnen Typen in Ihrem ADT zuzugreifen, ist ein abstrakten Typ Element zu verwenden anstelle eines generischen Typparameter.

sealed trait Foo { 
    type A 
    def method: A 
} 

case object Bar extends Foo { 
    type A = Array[String] 
    def method: A = Array.empty[String] 
} 

case object Baz extends Foo { 
    type A = Map[String, Array[String]] 
    def method: A = Map.empty[String, Array[String]] 
} 

case class Qux[X <: Foo](foo: X) { 
    def m: X#A = foo.method 

    // You can then pattern match on m 
    def f = m match { 
    case a: Baz.A => a.size // Use Baz#A if Baz is a class and not an object 
    case b: Bar.A => b.size // Use Bar#A if Bar is a class and not an object 
    } 
} 

Mit ihm (Blick auf die Rückgabetypen)

@ Qux(Baz).m 
res6: Map[String, Array[String]] = Map() 

@ Qux(Bar).m 
res7: Array[String] = Array() 
+0

Dies scheint ein sehr vielversprechender Ansatz zu sein; Danke! Ich habe jedoch den Compiler, der sich darüber beschwert, dass "Typ A kein Mitglied des Typparameters X" in der Definition von "Qux" ist. – user4601931

+0

Hmmm ... Welche Version verwendest du? Ich habe das am 2.11.8 versucht. Ich habe es auch noch einmal geschossen. – marios

+0

Verwenden Sie ': Einfügen' in Ihrer Shell und kopieren Sie dann den gesamten Code. Funktioniert das für dich? – marios

3

Sie könnten einen Typ-Parameter auf Ihre Eigenschaft wie folgt hinzu:

sealed trait Foo[A] { 
    def method: A 
} 

case class Bar extends Foo[Array[String]] { 
    def method: Array[String] 
} 

case class Baz extends Foo[Map[String, Array[String]]] { 
    def method: Map[String, Array[String]] 
} 
+0

Dies ist der einfachste Weg, dies zu tun! Gute Antwort. Ich bin mir jedoch nicht sicher, ob es sich wirklich um den zweiten Teil der Frage handelt. Wie würde Qux funktionieren? – marios

+0

Fügen Sie dieser Klasse ebenfalls eine Typklassenvariable hinzu. Etwas wie 'Klasse Qux [A]' oder vielleicht 'Klasse Qux [_]' wenn Sie sich nicht für den Typ interessieren – Tyler

+0

Aber er kümmert sich um den Typ. 'val m = foo.method' will das OP m den gleichen Typ A von' Foo [A] 'haben. Ich denke, etwas wie "Klasse Qux [A] (foo: Foo [A])" sollte den Trick machen. – marios

Verwandte Themen