Scala 中的类型安全通用案例 class 更新
Type-safe generic case class updates in Scala
我正在尝试编写一些代码来跟踪对记录的更改并在以后应用它们。在动态语言中,我会通过简单地保留 List[(String, Any)] 对的日志来做到这一点,然后在我最终决定提交更改时简单地将它们作为对原始记录的更新应用。
我需要能够自省更新,因此更新函数列表不合适。
在 Scala 中,使用反射这很简单,但是我想实现一个类型安全的版本。
我的第一次尝试是尝试使用 shapeless。如果我们知道特定的类型,这很有效。
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)
但是我不知道如何使这项工作通用。
trait Record[T]
def update( ??? ):T
}
考虑到 shapeless 处理这个问题的方式,我不确定这是否可能?
如果我接受很多样板,作为一个穷人版本,我可以按照以下方式做一些事情。
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 )
}
}
然而,这不是特别优雅,需要大量样板文件。我可能会编写自己的宏来处理这个问题,但这似乎是一件相当普遍的事情 - 有没有办法用 Shapeless 或类似的宏库来处理这个问题?
如何使用 class 的整个实例作为更新?
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))
}
我认为,如果你想要真正类型安全的解决方案,这是最干净、最易读的,而且可能是实施起来最不痛苦的,因为你不需要处理记录、镜头和个人字段描述符,只是 ContactUpdate(name="foo")
创建一个更新,然后 updates.map(Contact.update(target, _))
按顺序应用它们。
我正在尝试编写一些代码来跟踪对记录的更改并在以后应用它们。在动态语言中,我会通过简单地保留 List[(String, Any)] 对的日志来做到这一点,然后在我最终决定提交更改时简单地将它们作为对原始记录的更新应用。
我需要能够自省更新,因此更新函数列表不合适。
在 Scala 中,使用反射这很简单,但是我想实现一个类型安全的版本。
我的第一次尝试是尝试使用 shapeless。如果我们知道特定的类型,这很有效。
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)
但是我不知道如何使这项工作通用。
trait Record[T]
def update( ??? ):T
}
考虑到 shapeless 处理这个问题的方式,我不确定这是否可能?
如果我接受很多样板,作为一个穷人版本,我可以按照以下方式做一些事情。
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 )
}
}
然而,这不是特别优雅,需要大量样板文件。我可能会编写自己的宏来处理这个问题,但这似乎是一件相当普遍的事情 - 有没有办法用 Shapeless 或类似的宏库来处理这个问题?
如何使用 class 的整个实例作为更新?
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))
}
我认为,如果你想要真正类型安全的解决方案,这是最干净、最易读的,而且可能是实施起来最不痛苦的,因为你不需要处理记录、镜头和个人字段描述符,只是 ContactUpdate(name="foo")
创建一个更新,然后 updates.map(Contact.update(target, _))
按顺序应用它们。