Type-safe generic case class updates in Scala

Jam*_*ies 5 generics scala shapeless

I'm attempting to write some code that tracks changes to a record and applies them at a later date. In a dynamic language I'd do this by simply keeping a log of List[(String, Any)] pairs, and then simply applying these as an update to the original record when I finally decide to commit the changes.

I need to be able to introspect over the updates, so a list of update functions isn't appropriate.

In Scala this is fairly trivial using reflection, however I'd like to implement a type-safe version.

My first attempt was to try with shapeless. This works well if we know specific types.

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)
Run Code Online (Sandbox Code Playgroud)

However I can't figure out how to make this work generically.

trait Record[T]
    def update( ??? ):T 
}
Run Code Online (Sandbox Code Playgroud)

Given the way shapeless handles this, I'm not sure if this would even be possible?

如果我接受了很多样板文件,作为一个糟糕的版本,我可以按照以下方式做一些事情.

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 )
    }
}
Run Code Online (Sandbox Code Playgroud)

然而,这不是特别优雅,需要大量的样板.我可能会编写自己的宏来处理这个问题,但这似乎是一个相当普遍的事情 - 有没有办法用Shapeless或类似的宏库来处理它?

Dim*_*ima 1

使用类的整个实例作为更新怎么样?

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))
}
Run Code Online (Sandbox Code Playgroud)

我认为,如果您想要真正类型安全的解决方案,这是最干净、最易读的,而且可能是实现起来最少的痛苦,因为您不需要处理记录、镜头和单独的字段描述符,只需ContactUpdate(name="foo")创建更新,并按updates.map(Contact.update(target, _))顺序应用它们。