【问题标题】:Type-safe generic case class updates in ScalaScala 中的类型安全通用案例类更新
【发布时间】:2015-02-28 15:30:03
【问题描述】:

我正在尝试编写一些代码来跟踪记录的更改并在以后应用它们。在动态语言中,我只需保留 List[(String, Any)] 对的日志,然后在我最终决定提交更改时简单地将它们作为对原始记录的更新。

我需要能够自省更新,因此不适合列出更新函数。

在 Scala 中,使用反射是相当简单的,但是我想实现一个类型安全的版本。

我的第一次尝试是尝试无形。如果我们知道特定的类型,这很有效。

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 或类似的宏库来处理这个问题?

【问题讨论】:

  • 你看过光学(透镜、棱镜等)吗?
  • 如果我正确理解镜头,它们允许我显式更新记录,但不能记录关于我以后要应用的更改的元数据?
  • 我认为 shapeless 提供了您需要的大部分部件。您可以查看最新的无形 2.1.0 快照中的类型安全增量和案例类点菜示例。除此之外,您的问题还不够精确,以至于我可以在不为您写下全部内容的情况下回答它;-)
  • 谢谢。具体来说,我希望能够将包含一个或多个更新的数据结构传递给类型安全庄园中的案例类,以用于某些原始事务逻辑。 “类型安全增量”一词听起来几乎正是我正在寻找的 - 我将尝试使用最新的快照。
  • 其实早先关于镜头的评论给我指明了正确的方向——我需要做的就是传递一个 (lens[Record].field, value) 的元组,然后在准备好时应用它。

标签: scala generics shapeless


【解决方案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))
}

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

【讨论】:

    猜你喜欢
    • 2014-07-29
    • 1970-01-01
    • 2016-04-13
    • 1970-01-01
    • 2016-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多