2015-02-23 3 views
5

Ich möchte eine Laufzeitumwandlung in einen generischen Code (asInstanceOf[A]) ohne implizite Konvertierungen loswerden.Rückgabekopie von Fallklasse aus generischer Funktion ohne Laufzeitumwandlung

Dies passiert, wenn ich ein ziemlich sauberes Datenmodell habe, das aus Fallklassen mit einem gemeinsamen Merkmal besteht und einen generischen Algorithmus implementieren möchte. Als Beispiel sollte der resultierende Algorithmus eine Klasse vom Typ A nehmen, die eine Unterklasse der trait T ist und eine Kopie der konkreten Klasse A mit einem aktualisierten Feld zurückgeben soll.

Dies ist einfach zu erreichen, wenn ich einfach eine abstrakte copy -Methode zum Basismerkmal hinzufügen und diese in allen Unterklassen implementieren kann. Dies verschmutzt jedoch das Modell möglicherweise mit Methoden, die nur von bestimmten Algorithmen benötigt werden und ist manchmal nicht möglich, weil das Modell außerhalb meiner Kontrolle sein könnte.

Hier ist ein vereinfachtes Beispiel zur Veranschaulichung des Problems und eine Lösung, die Laufzeitumwandlungen verwendet.

Bitte nicht auf die Details hängen.

Angenommen, es ist ein Merkmal und einige Fallklassen ich nicht ändern kann:

trait Share { 
    def absolute: Int 
} 

case class CommonShare(
    issuedOn: String, 
    absolute: Int, 
    percentOfCompany: Float) 
    extends Share 

case class PreferredShare(
    issuedOn: String, 
    absolute: Int, 
    percentOfCompany: Float) 
    extends Share 

Und hier ist eine einfache Methode, um die aktuellen percentOfCompany neu zu berechnen, wenn die Gesamtzahl der Aktien geändert haben und aktualisiert das Feld in der Fall Klasse

def recalculateShare[A <: Share](share: A, currentTotalShares: Int): A = { 

    def copyOfShareWith(newPercentage: Float) = { 
    share match { 
     case common: CommonShare => common.copy(percentOfCompany = newPercentage) 
     case preferred: PreferredShare => preferred.copy(percentOfCompany = newPercentage) 
    } 
    } 

    copyOfShareWith(share.absolute/currentTotalShares.toFloat).asInstanceOf[A] 
} 

Einige Beispiel Anrufungen auf dem REPL:

scala> recalculateShare(CommonShare("2014-01-01", 100, 0.5f), 400) 
res0: CommonShare = CommonShare(2014-01-01,100,0.25) 

scala> recalculateShare(PreferredShare("2014-01-01", 50, 0.5f), 400) 
res1: PreferredShare = PreferredShare(2014-01-01,50,0.125) 

So funktioniert es und so weit ich verstehe, der .asInstanceOf[A] Aufruf wird nie fehlschlagen, ist aber erforderlich, um den Code kompilieren zu machen. Gibt es eine Möglichkeit, die Laufzeitumwandlung ohne implizite Konvertierungen typsicher zu vermeiden?

+0

Ich glaube nicht. Sie würden einen impliziten Beweis des typetag benötigen. Andernfalls erhalten Sie ein Problem beim Löschen des Typs. –

Antwort

4

Sie haben ein paar Möglichkeiten, die ich mir vorstellen kann, und es kommt meistens auf eine Balance von wie allgemein einer Lösung, die Sie wollen und wie viel Ausführlichkeit Sie tolerieren können.

asInstanceOf

Ihre Lösung fühlt sich schmutzig, aber ich glaube nicht, dass es so schlimm, und die gnarliness ist ziemlich gut enthalten.

typeclass

Ein großer Anfahrverhalten zu Datentypen bereitgestellt wird, während nach wie vor Trennung von Bedenken im Code Aufrechterhaltung der Enrich Your Library/typeclass Muster. Ich wünschte, ich hätte eine perfekte Referenz dafür, aber ich nicht. Schlagen Sie diese Begriffe oder "implizite Klasse" nach, und Sie sollten in der Lage sein, genügend Beispiele zu finden, um die Drift zu bekommen.

Sie können eine trait Copyable[A] { def copy(?): A } Typenklasse (implicit class) erstellen und Instanzen davon für jeden Ihrer Typen erstellen. Das Problem hier ist, dass es ziemlich ausführlich ist, besonders wenn Sie möchten, dass die Methode copy vollständig generisch ist. Ich habe seine Parameterliste als Fragezeichen hinterlassen, weil man sie genau an das anpassen könnte, was man wirklich braucht, oder man könnte versuchen, es für irgendeinen case class zu machen, was meines Wissens ziemlich schwierig wäre.

Optics

Linsen wurden zur Lösung dieser Art von Peinlichkeit gemacht. Vielleicht möchten Sie überprüfen Monocle, die eine nette generische Ansatz für dieses Problem ist. Obwohl es das Problem der Ausführlichkeit immer noch nicht wirklich löst, könnte es der richtige Weg sein, wenn sich dieses Problem in Ihrem gesamten Projekt wiederholt, insbesondere wenn Sie versuchen, tief in Ihrem Objektdiagramm Änderungen vorzunehmen.

+0

Sehr gut geschriebene Antwort, danke! Ich muss Monocle ausprobieren, aber ich nehme an, dass ich die Linse in meine generische Funktion neben den anderen Parametern einbringen muss, was den Client-Code meiner generischen Funktion komplizierter macht. Soweit Typklassen gehen, habe ich diesen Ansatz versucht, aber es hat nicht wirklich funktioniert. Meine Versuche warnten mich immer davor, implizite Conversions zu aktivieren, was genau das war, was ich vermeiden wollte. Liege ich falsch? –

+0

Nicht sicher, ich habe Monocle nicht wirklich versucht. Es kann automatisch Linsen erzeugen, aber es scheint, als wäre es ideal, wenn diese irgendwie implizit miteinander verbunden werden könnten. Und in Ihrem Fall scheint es, als wäre es ideal, wenn es eine einfache Möglichkeit gäbe, ein Objektiv auf der gemeinsamen Schnittstelle zu definieren. IMO, Scala tut ein bisschen weh, weil er keine direkte Unterstützung dafür hat. – acjay