2015-04-10 2 views
8

Ich habe einige Code wie folgt:Wie kann ich eine Funktion schreiben, die einen polymorphen Rückgabetyp hat, basierend auf dem Typargument seines Typparameters?

sealed trait Foo[A] { 
    def value: A 
} 
case class StringFoo(value: String) extends Foo[String] 
case class IntFoo(value: Int) extends Foo[Int] 

Ich möchte eine Funktion haben, die den A Typ einen Subtyp des Typs Parameter angegeben verwenden können.

// Hypothetical invocation 
val i: Int = dostuff[IntFoo](param) 
val s: String = dostuff[StringFoo](param) 

Ich kann nicht herausfinden, wie dostuff in einer Art und Weise zu erklären, die funktioniert. Das nächste, was ich herausfinden kann, ist

def dostuff[B <: Foo[A]](p: Param): A 

Aber das funktioniert nicht, weil A in dieser Position nicht definiert ist. Ich kann so etwas wie

def dostuff[A, B <: Foo[A]](p: Param): A 

tun, aber dann habe ich es wie dostuff[String, StringFoo](param) aufrufen, die ziemlich hässlich ist.

Es scheint, wie der Compiler sollten alle Informationen, es A hinüber zum Rückgabetyp bewegen muss, wie kann ich diese Arbeit machen, entweder in Standard-scala oder mit einer Bibliothek. Ich bin gerade auf Scala 2.10, wenn sich das auf die Antwort auswirkt. Ich bin offen für eine 2,11-only Lösung, wenn es möglich ist, es aber unmöglich in 2.10

Antwort

6

Wie Sie vielleicht wissen, wenn Sie einen Parameter vom Typ Foo[A] haben, dann können Sie die Methode generisch machen in nur A:

def dostuff[A](p: Foo[A]): A = ??? 

Da dies möglicherweise nicht immer der Fall ist, können wir versuchen, einen impliziten Parameter zu verwenden, der die Beziehung zwischen A und B ausdrücken kann. Da wir nicht nur einige der generischen Parameter auf einen Methodenaufruf anwenden können (generische Parameter-Inferenz ist alles oder nichts), müssen wir diese in zwei Aufrufe aufteilen. Dies ist eine Option:

case class Stuff[B <: Foo[_]]() { 
    def get[A](p: Param)(implicit ev: B => Foo[A]): A = ??? 
} 

Sie die Typen in der REPL überprüfen:

:t Stuff[IntFoo].get(new Param) //Int 
:t Stuff[StringFoo].get(new Param) //String 

Eine weitere Option auf der gleichen Linie, sondern eine anonyme Klasse verwendet wird, ist:

def stuff[B <: Foo[_]] = new { 
    def apply[A](p: Param)(implicit ev: B <:< Foo[A]): A = ??? 
} 

:t stuff[IntFoo](new Param) //Int 

Hier habe ich apply anstelle von get verwendet, so dass Sie die Methode natürlicher anwenden können. Wie in Ihrem Kommentar vorgeschlagen, habe ich hier <:< im Beweistyp verwendet. Für diejenigen, die mehr über diese Art von verallgemeinerter Typ Constraint erfahren möchten, können Sie mehr lesen here.

Sie könnten auch abstrakte Member anstelle von generischen Parametern verwenden. Wenn es mit generischen Typinferenzfällen kämpft, bietet dies oft eine elegante Lösung. Sie können mehr über Mitglieder des abstrakten Typs und ihre Beziehung zu Generika lesen here.

+0

Dies ist, was ich ging, aber anstelle von 'ev: B => Foo [A]' ich verwendet 'ev: B <: Daenyth

5

Eine weitere Option ist der Typ Mitglieder zu verwenden:

sealed trait Foo { 
    type Value 
    def value: Value 
} 

case class StringFoo(value: String) extends Foo { type Value = String } 
case class IntFoo(value: Int) extends Foo { type Value = Int } 

def dostuff[B <: Foo](p: Any): B#Value = ??? 

// Hypothetical invocation 
val i: Int = dostuff[IntFoo](param) 
val s: String = dostuff[StringFoo](param) 

Beachten Sie, dass beide Lösungen in erster Linie um die syntaktische Einschränkung in Scala arbeiten, dass Sie nicht ein Typ Parameter einer Liste beheben können und der Compiler die andere schließen.

+0

Dies ist in der Tat * die * richtige Lösung. Da der Autor der Frage einen Typ erhalten möchte, der einem anderen Typ zugeordnet ist, wird implizit angenommen, dass diese Beziehung eindeutig ist - für jeden Quelltyp gibt es einen Zieltyp. Dies ist genau das, was abstrakte Typ-Mitglieder/assoziierte Typen sind. –

+0

Ich versuchte dies, aber für meinen speziellen Anwendungsfall bekam ich Fehler, die aussahen, als würde es eine pfadabhängige Typisierung machen, wobei der Wert eines StringFoo nicht gleich dem Wert eines anderen war – Daenyth

Verwandte Themen