如何使用 LabelledGeneric 一般更新案例类字段? [英] How to generically update a case class field using LabelledGeneric?

查看:43
本文介绍了如何使用 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆