如何使用 LabelledGeneric 一般更新案例类字段? [英] How to generically update a case class field using LabelledGeneric?
问题描述
使用 shapeless,可以使用 LabelledGeneric
更新案例类字段,如下所示:
Using shapeless, one can use LabelledGeneric
to update case class fields like so:
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)
我希望 Test
类(和其他类)扩展抽象类 Model
,它将实现一个方法 withId
将使用一个 LabelledGeneric
类似于上面的代码来更新 id
字段,如果它有一个(它应该).
I would like the Test
class (and others) to extend an abstract class Model
, that will implement a method withId
that would use a LabelledGeneric
similar to the above code to update the id
field, should it have one (which it should).
我的尝试将 LabelledGeneric[A]
的隐式参数添加到 Model
的构造函数中,它实现得很好.我还需要以某种方式向记录语法提供证据,证明 LabelledGeneric#Repr
具有要替换的 id
字段.给withId
添加一个隐含的Updater
参数满足编译器,这样下面的代码会编译,但是不可用.
My attempt adds an implicit parameter of a LabelledGeneric[A]
to the constructor of Model
, which materializes just fine. I also need to somehow provide evidence to the record syntax that the LabelledGeneric#Repr
has the id
field to replace. Adding an implicit Updater
parameter to withId
satisfies the compiler, so that the code below will compile, but it is not usable.
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]
当调用 test.withId(1)
时,隐含的 Updater
无法实现.宏报告 gen.Repr
不是 HList
类型,而实际上是.似乎 是失败的那个,其中 u baseType HConsSym
返回
.相当于:
When calling test.withId(1)
, the implicit Updater
fails to materialize. The macro reports that gen.Repr
isn't an HList
type, when it in fact is. It seems that this match is the one that fails, where u baseType HConsSym
returns <notype>
. Equivalent to:
scala> weakTypeOf[test.gen.Repr].baseType(weakTypeOf[::[_, _]].typeConstructor.typeSymbol)
res12: reflect.runtime.universe.Type = <notype>
这是使用 shapeless 2.3,虽然它在 2.2 中由于不同的原因失败(似乎 Updater
有一个很大的重构).
This is using shapeless 2.3, though it fails for different reasons in 2.2 (seems as though Updater
had a large refactor).
是否有可能用无形的方式来实现这一点,或者我离目标很远?
Is it possible to accomplish this with shapeless, or am I way off target?
推荐答案
这里的主要问题是 LabelledGeneric (Repr
) 的精炼结果类型丢失了.在Model
,关于Repr
唯一已知的是Repr <: HList
.隐含的 Updater.Aux[gen.Repr, F, gen.Repr]
搜索仅称为 _ <: HList
的内容,因此无法实现.您必须使用两个类型参数定义 Model
抽象类Model[A, L <: HList](隐式gen: LabelledGeneric.Aux[A, L])
但这不允许您编写 class Test extends Model[Test]
并且您必须手动编写标记的泛型类型.
The main issue here is that the refined result type of the LabelledGeneric (Repr
) is lost. At Model
, the only thing known about Repr
is Repr <: HList
. The implicit Updater.Aux[gen.Repr, F, gen.Repr]
searches for something that is only known as _ <: HList
and thus fails to materialize.
You'd have to define Model
with two type parameters
abstract class Model[A, L <: HList](implicit gen: LabelledGeneric.Aux[A, L])
but this doesn't allow you to write class Test extends Model[Test]
and you have to write the labelled generic type by hand.
如果你改为将 gen
下移到 withId
,你可以让它工作:
If you instead move the gen
down to withId
, you can make it work:
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]
如果您关心分辨率性能,可以在 Test
的随附文件中缓存值:
If you're concerned with resolution performance, you can cache the value(s) in the companion of 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))
将使用 Test.gen
的定义,而不是每次都实现一个新的 LabelledGeneric
.
would use the definition of Test.gen
instead of materializing a new LabelledGeneric
every time.
这对 shapeless 2.2.x 和 2.3.x 都有效.
This works for both, shapeless 2.2.x and 2.3.x.
这篇关于如何使用 LabelledGeneric 一般更新案例类字段?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!