Scala 如何使用LabelledGeneric更新案例类字段?

Scala 如何使用LabelledGeneric更新案例类字段?,scala,shapeless,Scala,Shapeless,使用shapeless,可以使用LabelledGeneric更新案例类字段,如下所示: case class Test(id: Option[Long], name: String) val test = Test(None, "Name") val gen = LabelledGeneric[Test] scala> gen.from(gen.to(test) + ('id ->> Option(1L))) res0: Test = Test(Some(1),Name)

使用shapeless,可以使用
LabelledGeneric
更新案例类字段,如下所示:

case class Test(id: Option[Long], name: String)
val test = Test(None, "Name")
val gen = LabelledGeneric[Test]

scala> gen.from(gen.to(test) + ('id ->> Option(1L)))
res0: Test = Test(Some(1),Name)
我希望
测试
类(和其他类)扩展一个抽象类
模型
,该类将实现一个id为
的方法,该方法将使用类似于上述代码的
标签来更新
id
字段,如果它有一个字段(应该有)

我的尝试是向
模型
的构造函数添加一个
LabelledGeneric[a]
的隐式参数,该参数很好地具体化了。我还需要以某种方式为记录语法提供证据,
LabelledGeneric#Repr
具有要替换的
id
字段。将隐式的
更新程序
参数添加到
withId
可以满足编译器的要求,因此下面的代码可以编译,但不可用

import shapeless._, record._, ops.record._, labelled._, syntax.singleton._, tag._

abstract class Model[A](implicit gen: LabelledGeneric[A] { type Repr <: HList }) { this: A =>

    def id: Option[Long]

    val idWitness = Witness("id")

    type F = FieldType[Symbol with Tagged[idWitness.T], Option[Long]]

    def withId(id: Long)(implicit u: Updater.Aux[gen.Repr, F, gen.Repr]) =
        gen.from(gen.to(this) + ('id ->> Option(id)))

}

case class Test(id: Option[Long], name: String) extends Model[Test]
这是在使用shapeless 2.3,尽管在2.2中由于不同的原因失败了(似乎
Updater
有一个大的重构)


是否有可能通过不成形来实现这一点,或者我偏离了目标?

这里的主要问题是LabelledGeneric(
Repr
)的优化结果类型丢失。在
Model
上,关于
Repr
的唯一已知信息是
Repr
scala> weakTypeOf[test.gen.Repr].baseType(weakTypeOf[::[_, _]].typeConstructor.typeSymbol)
res12: reflect.runtime.universe.Type = <notype>
object Model {
  private type IdField = Symbol with Tagged[Witness.`"id"`.T]
  private val  IdField = field[IdField]

  type F = FieldType[IdField, Option[Long]]
}
abstract class Model[A] { this: A =>
  import Model._

  def id: Option[Long]

  def withId[L <: HList](id: Long)(implicit   // L captures the fully refined `Repr`
    gen: LabelledGeneric.Aux[A, L],           // <- in here ^
    upd: Updater.Aux[L, F, L]                 // And can be used for the Updater
  ): A = {
    val idf = IdField(Option(id))
    gen.from(upd(gen.to(this), idf))
  }
}

case class Test(id: Option[Long], name: String) extends Model[Test]
case class Test(id: Option[Long], name: String) extends Model[Test]
object Test {
  implicit val gen = LabelledGeneric[Test]
}
val test = Test(None, "Name")
println("test.withId(12) = " + test.withId(12))
println("test.withId(12).withId(42) = " + test.withId(12).withId(42))