2015-01-02 16 views
5

Ich versuche, Code zu schreiben, der Änderungen in einem Datensatz verfolgt und sie zu einem späteren Zeitpunkt anwendet. In einer dynamischen Sprache würde ich dies tun, indem ich einfach ein Protokoll von List [(String, Any)] -Paaren führe und diese dann einfach als Aktualisierung auf den ursprünglichen Datensatz anwende, wenn ich mich schließlich dazu entschließe, die Änderungen zu übernehmen.Typsichere generische Fallklassenaktualisierung in Scala

Ich muss in der Lage sein, über die Updates zu inspizieren, so dass eine Liste der Update-Funktionen nicht geeignet ist.

In Scala ist dies mit Reflektion ziemlich trivial, aber ich möchte eine typsichere Version implementieren.

Mein erster Versuch war es mit formlos zu versuchen. Das funktioniert gut, wenn wir bestimmte Typen kennen.

import shapeless._ 
import record._ 
import syntax.singleton._ 

case class Person(name:String, age:Int) 
val bob = Person("Bob", 31) 
val gen = LabelledGeneric[Person] 

val updated = gen.from(gen.to(bob) + ('age ->> 32)) 

// Result: Person("Bob", 32) 

Jedoch kann ich nicht herausfinden, wie man diese Arbeit generisch macht.

trait Record[T] 
    def update(???):T 
} 

Angesichts der Art und Weise formlos behandelt dies, ich bin mir nicht sicher, ob das überhaupt möglich wäre?

Wenn ich eine Menge Boilerplate akzeptiere, könnte ich als armer mans Version etwas tun, was den folgenden Punkten entspricht.

object Contact { 
    sealed trait Field[T] 
    case object Name extends Field[String] 
    case object Age extends Field[Int] 
} 

// A typeclass would be cleaner, but too verbose for this simple example. 
case class Contact(...) extends Record[Contact, Contact.Field] { 
    def update[T](field:Contact.Field[T], value:T) = field match {   
     case Contact.Name => contact.copy(name = value) 
     case Contact.Age => contact.copy(age = value) 
    } 
} 

Dies ist jedoch nicht besonders elegant und erfordert eine Menge Standard. Ich könnte wahrscheinlich mein eigenes Makro schreiben, um damit umzugehen, aber es scheint eine ziemlich gewöhnliche Sache zu sein - gibt es eine Möglichkeit, dies bereits mit Shapeless oder einer ähnlichen Makrobibliothek zu handhaben?

+0

Haben Sie sich die Optik (Linsen, Prismen usw.) angesehen? – rightfold

+0

Wenn ich Objektive richtig verstehe, erlauben sie mir, den Datensatz explizit zu aktualisieren, aber keine Metadaten über Änderungen, die ich später anwenden möchte, zu protokollieren. –

+3

Ich denke, formlos bietet die meisten Teile, die Sie brauchen. Sie können die typsicheren Delta- und Case-Class-A-la-Carte-Beispiele im neuesten formlosen 2.1.0-Snapshot betrachten. Darüber hinaus ist deine Frage nicht präzise genug, dass ich sie beantworten könnte, ohne dir die ganze Sache zu schreiben ;-) –

Antwort

1

Wie wäre es mit der gesamten Instanz der Klasse als Update?

case class Contact(name: String, age: Int) 
case class ContactUpdate(name: Option[String] = None, age: Option[Int] = None) 

object Contact { 
    update(target: Contact, delta: ContactUpdate) = Contact(
     delta.name.getOrElse(target.name) 
     target.age.getOrElse(delta.age) 
    ) 
} 
// also, optionally this: 
object ContactUpdate { 
    apply(name: String) = ContactUpdate(name = Option(name)) 
    apply(age: Int) = ContactUpdate(age = Option(age)) 
} 

Ich denke, wenn Sie die wirklich typsichere Lösung wollen, ist dies die sauberste und am besten lesen, und auch, möglicherweise der Schmerz zu implementieren, wie Sie brauchen, um mit Aufzeichnungen nicht umgehen, Linsen und einzelne Felddeskriptoren, nur ContactUpdate(name="foo") erstellt ein Update, und updates.map(Contact.update(target, _)) gilt sie alle nacheinander.

Verwandte Themen