2014-04-21 4 views
6

Angenommen, wir haben ein gemeinsames Merkmal Modell.Ist es möglich Felder jeder Fallklasse zu aktualisieren, die ein gemeinsames Merkmal implementieren

trait Model { 
    def id: String 
    def updated: Date 
} 

Und wir haben 2 Fallklassen, die dieses Merkmal erweitern.

case class C1(id: String, updated: Date, foo: String) extends Model 
case class C2(id: String, updated: Date, bar: Int) extends Model 

Ist es möglich, eine Nutzenfunktion wie unter denen ein Modell als Parameter nimmt zu schreiben und gibt eine Kopie mit einem aktualisierten Wert für das aktualisierte Feld?

+1

[Nein] (http://stackoverflow.com/q/12370244/298389) –

+2

@ om-nom-nom ja ;-) –

+1

@MilesSabin [Ich habe diese Abstraktion gesehen über arity wurde erwähnt] (http://StackOverflow.com/a/12492635/298389), aber solch ein Overengineering ist kaum eine praktikable Option (jetzt können Sie mich frei 'voreingenommen' oder sogar einen Idioten nennen :-)). Oder gibt es noch einen anderen Weg? –

Antwort

1

copy ist eine Methode, die für Ihre Fallklassen definiert ist. Nicht auf Ihrem Basismerkmal Model. Was passiert, wenn Sie haben, dass:

trait Model { 
    def id: String 
    def updated: Date 
} 

case class C1(id: String, updated: Date, foo: String) extends Model 
case class C2(id: String, updated: Date, bar: Int) extends Model 
class NotACaseClass(val id: String, val updated: Date) extends Model 

NotACaseClass ein sehr gültiges Kind Model ist, und man konnte eine Instanz davon auf Ihre update Funktion, aber viel Glück der Suche nach einem copy Methode :)

+2

Es antwortet * warum es nicht funktioniert * aber bietet keine Lösung für das gegebene Problem. –

3

Ihr Code übergeben zwei Probleme hat:

  1. copy ist nicht auf das Merkmal definiert, so dass Sie etwas auf dem Merkmal definiert haben müssen, die Sie verwenden können.
  2. Damit update eine T anstelle einer Model zurückgibt, muss jeder Model seinen tatsächlichen Untertyp kennen.

Sie können es wie folgt beheben:

trait Model[T <: Model[T]] { 
    def id: String 
    def updated: Date 
    def withDate(d: Date): T 
} 

case class C1(id: String, updated: Date, foo: String) extends Model[C1] { def withDate(d: Date) = copy(updated = d) } 
case class C2(id: String, updated: Date, bar: Int) extends Model[C2] { def withDate(d: Date) = copy(updated = d) } 

object Model { 
    def update[T <: Model[T]](model: T): T = { 
    model.withDate(new Date) // This code does not compile. 
    } 
} 

So, jetzt funktioniert es:

scala> val c1 = C1("test", new Date, "foo") 
c1: C1 = C1(test,Mon Apr 21 10:25:10 CDT 2014,foo) 

scala> Model.update(c1) 
res0: C1 = C1(test,Mon Apr 21 10:25:17 CDT 2014,foo) 
+0

Das war die Art, wie ich vorgehen wollte. Aber ich frage mich immer noch, ob es einen Weg gibt, dies automatisch zu tun und sicher zu tippen. – fcs

+0

Obwohl "withDate" für jeden Subtyp von 'Model' die gleiche Codezeile ist, muss sie für jede Klasse neu geschrieben werden, richtig? –

3

Die "beste" Abstraktion Sie hier schreiben kann eine Lens wäre das wie folgt aussieht:

trait Lens[A, B]{ 
    def get: A => B 
    def set: (A, B) => A 
} 

so dass Ihr Code wie folgt aussieht:

def update[A](that: A, value: Date)(implicit tLens: Lens[A, Date]): A = 
    tLens set (that, value) 
+0

Eine Linse ist gut ... aber ich denke, wir können wahrscheinlich die Linse mit einem formlosen 'LabelledGeneric' für die in Frage stehenden Fallklassen _infer_inferieren. –

+1

@MilesSabin Ja, ich wette, du hast Recht. Wenn du es schreibst, werde ich diese Antwort auf den Tisch legen. – wheaties

Verwandte Themen