如何使用 LabelledGeneric 一般更新 case class 字段?
How to generically update a case class field using LabelledGeneric?
使用 shapeless,可以使用 LabelledGeneric
更新大小写 class 字段,如下所示:
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
class(和其他人)扩展一个抽象 class Model
,它将实现一个方法 withId
使用类似于上述代码的 LabelledGeneric
来更新 id
字段,如果它有一个(它应该)。
我尝试将 LabelledGeneric[A]
的隐式参数添加到 Model
的构造函数中,实现得很好。我还需要以某种方式为 LabelledGeneric#Repr
具有要替换的 id
字段的记录语法提供证据。在withId
中添加一个隐含的Updater
参数可以满足编译器的要求,这样下面的代码可以编译,但是不能使用。
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
类型,而实际上是。似乎 this match 是失败的,其中 u baseType HConsSym
returns <notype>
。相当于:
scala> weakTypeOf[test.gen.Repr].baseType(weakTypeOf[::[_, _]].typeConstructor.typeSymbol)
res12: reflect.runtime.universe.Type = <notype>
这是使用 shapeless 2.3,尽管它在 2.2 中由于不同的原因而失败(似乎 Updater
进行了较大的重构)。
是否可以用 shapeless 来完成这个,或者我偏离目标了吗?
这里的主要问题是 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]
如果您关心解析性能,可以将值缓存在 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
。
这适用于无形 2.2.x 和 2.3.x.
使用 shapeless,可以使用 LabelledGeneric
更新大小写 class 字段,如下所示:
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
class(和其他人)扩展一个抽象 class Model
,它将实现一个方法 withId
使用类似于上述代码的 LabelledGeneric
来更新 id
字段,如果它有一个(它应该)。
我尝试将 LabelledGeneric[A]
的隐式参数添加到 Model
的构造函数中,实现得很好。我还需要以某种方式为 LabelledGeneric#Repr
具有要替换的 id
字段的记录语法提供证据。在withId
中添加一个隐含的Updater
参数可以满足编译器的要求,这样下面的代码可以编译,但是不能使用。
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
类型,而实际上是。似乎 this match 是失败的,其中 u baseType HConsSym
returns <notype>
。相当于:
scala> weakTypeOf[test.gen.Repr].baseType(weakTypeOf[::[_, _]].typeConstructor.typeSymbol)
res12: reflect.runtime.universe.Type = <notype>
这是使用 shapeless 2.3,尽管它在 2.2 中由于不同的原因而失败(似乎 Updater
进行了较大的重构)。
是否可以用 shapeless 来完成这个,或者我偏离目标了吗?
这里的主要问题是 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]
如果您关心解析性能,可以将值缓存在 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
。
这适用于无形 2.2.x 和 2.3.x.