2016-06-02 14 views
3

Ich versuche, eine Scala-Funktion zu implementieren, die userFor ähnlich ist, wie in dem folgenden Codefragment gezeigt, wobei der Rückgabetyp der Funktion vom Typ des Eingabeparameters abhängt. In diesem Beispiel meldet der Scala-Compiler "Der Ausdruck vom Typ System1User.type entspricht nicht dem erwarteten Typ SystemUser[S]".Zurückgegebenes Objekt basierend auf dem Eingabetyp

Gibt es eine Möglichkeit, eine Factory-Methode wie userFor zu erstellen, die den Kompilierzeittyp der Eingabe verwendet, um den richtigen Ausgabetyp zurückzugeben?

sealed trait System { 
    def name: String 
} 
case object System1 extends System { def name = "1" } 
case object System2 extends System { def name = "2" } 

sealed trait SystemUser[S <: System] { 
    def use(s: S): String 
} 
object System1User extends SystemUser[System1.type] { 
    override def use(s: System1.type) = s"${s.name} is in use" 
} 
object System2User extends SystemUser[System2.type] { 
    override def use(s: System2.type) = s"${s.name} is in use" 
} 

object SystemUser { 
    //TODO: how to make the types work out here? 
    def userFor[S <: System](sys: S): SystemUser[S] = { 
    sys match { 
     case System1 => System1User 
     case System2 => System2User 
    } 
    } 

} 

+0

diese beantwortet wird? – slouc

Antwort

0

Ich entschied, dass in meinem Fall ist es ausreichend war es nur:

object SystemUser { 

    def userFor(sys: System1.type): SystemUser[System1.type] = { 
    sys match { 
     case System1 => System1User 
    } 
    } 

    def userFor(sys: System2.type): SystemUser[System2.type] = { 
    sys match { 
     case System2 => System2User 
    } 
    } 

} 
+0

Offensichtlich ist der reale Anwendungsfall komplizierter als das - es gibt tatsächlich mehrere Dinge, die System1 und System2 verwenden, daher nehmen die Factory-Funktionen 'userFor' mehrere Parameter an, aber der Versand basiert auf dem Typ des ersten Arguments lässt mich den Rückgabetyp angeben, und der Anrufer verwendet die Funktion in der Weise, die ich sowieso zu erreichen hoffte. –

2

Problem ist die Tatsache, dass Sie ein SystemUser[S] von userFor, wo S einige Subtyp von System zurückzukehren versprach. Aber Sie können dieses Versprechen nicht halten. Sie geben nicht zurück SystemUser[S]; Sie geben entweder SystemUser[System1.type] oder SystemUser[System2.type] zurück. Was würde passieren, wenn S etwas anderes als einer dieser beiden Typen ist? Was passiert, wenn jemand userFor aufgerufen hat, der mit SomeNewSystemSubtype parametrisiert wurde (sagen wir, dass es ein gültiger Subtyp von System ist)?

können Sie die Abhängigkeit entfernen S zu geben und einfach zurückgeben „einige Subtyp von System“:

def userFor[S <: System](sys: S): SystemUser[_ <: System] = { 

sauberere Lösung wäre SystemUser covariant in seiner Art Parameter zu machen, weil dann SystemUser[System1.type] eine Unterklasse von SystemUser[System] wäre , was bedeutet, würden Sie in der Lage sein, dies zu tun:

sealed trait SystemUser[+S <: System] { 
    def use(s: S): String 
} 
... 
def userFor[S <: System](sys: S): SystemUser[System] = { 

Aber leider Ihre Methode def use(s: S): String einen Parameter vom Typ S, und das ist eine kontravariante Position. Ich bin mir nicht sicher, ob Sie mit diesem Zeug vertraut sind, aber das zu tun ist nicht einfach - Sie müssten Ihre Methode in etwas wie def use[T >: S](s: T): String ändern, was Änderungen in den Implementierungen erfordern würde (da jetzt jeder Typ über S als erscheinen kann ein Parameter) und in Ihrem Fall ist dies wahrscheinlich nicht wert.

+0

Ich spiele damit ein bisschen herum. In meiner realen Codebasis habe ich die Macht, fast alle diese Eigenschaften und Klassen zu ändern. Wenn Sie den Rückgabetyp auf 'SystemUser [_ <: System]' umstellen, wird der Code kompiliert, die zurückgegebenen Objekte werden jedoch unbrauchbar, da 'SystemUser.userFor (System1) .use (System1)' nicht kompiliert wird. –

+0

Entschuldigung, Sie haben völlig Recht; Compiler weiß nicht, was der zugrunde liegende Typ ist. Ich glaube nicht, dass Sie das mit nur einer userFor-Methode lösen können, weil es zwei Objekte unterschiedlichen Typs zurückgibt und Sie einige Typinformationen verlieren müssen. Sie können nicht sagen "Rückgabetyp ist entweder vom Typ A oder B", Sie können nur sagen, "Rückgabetyp ist vom Typ C, das ist eine Oberklasse von A und B", aber in diesem Fall verlieren Sie Informationen, wie Sie selbst erlebt haben ob es tatsächlich A oder B darunter ist. – slouc

+1

Ja. Aber danke für deine Hilfe. Ich werde meine eigene Antwort unten genehmigen, aber ich wäre nicht ganz dahin gekommen, ohne an deinen Antworten herumzubasteln! –

Verwandte Themen