使用 shapeless,可以LabelledGeneric
像这样更新 case 类字段:
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)
Run Code Online (Sandbox Code Playgroud)
我希望Test
类(和其他类)扩展一个抽象类Model
,该类将实现一个方法withId
,该方法将使用LabelledGeneric
类似于上述代码来更新id
字段,如果它有一个(它应该)。
我的尝试将 a 的隐式参数添加LabelledGeneric[A]
到 的构造函数中Model
,它实现得很好。我还需要以某种方式为LabelledGeneric#Repr
具有id
要替换的字段的记录语法提供证据。添加一个隐式Updater
参数来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]
Run Code Online (Sandbox Code Playgroud)
调用时test.withId(1)
,隐式Updater
无法实现。宏报告这gen.Repr
不是HList
类型,而实际上是。似乎这场比赛是失败的,在那里u baseType HConsSym
返回<notype>
。相当于:
scala> weakTypeOf[test.gen.Repr].baseType(weakTypeOf[::[_, _]].typeConstructor.typeSymbol)
res12: reflect.runtime.universe.Type = <notype>
Run Code Online (Sandbox Code Playgroud)
这是使用 shapeless 2.3,尽管它在 2.2 中由于不同的原因失败(似乎Updater
有很大的重构)。
是否有可能通过无形来实现这一目标,还是我离目标很远?
这里的主要问题是 LabelledGeneric ( Repr
)的精炼结果类型丢失了。在Model
,唯一已知的Repr
是Repr <: HList
。隐式Updater.Aux[gen.Repr, F, gen.Repr]
搜索仅被称为_ <: HList
并因此无法实现的东西。您必须Model
使用两个类型参数进行定义,
abstract class Model[A, L <: HList](implicit gen: LabelledGeneric.Aux[A, L])
但这不允许您编写class Test extends Model[Test]
,您必须手动编写标记的泛型类型。
如果您改为将gen
下移到withId
,则可以使其工作:
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]
Run Code Online (Sandbox Code Playgroud)
如果您关心分辨率性能,您可以将值缓存在 的伴随中Test
:
case class Test(id: Option[Long], name: String) extends Model[Test]
object Test {
implicit val gen = LabelledGeneric[Test]
}
Run Code Online (Sandbox Code Playgroud)
这意味着像这样的代码
val test = Test(None, "Name")
println("test.withId(12) = " + test.withId(12))
println("test.withId(12).withId(42) = " + test.withId(12).withId(42))
Run Code Online (Sandbox Code Playgroud)
将使用的定义Test.gen
而不是LabelledGeneric
每次都实现一个新的。
这适用于 shapeless 2.2.x 和 2.3.x。