【问题标题】:How to generically update a case class field using LabelledGeneric?如何使用 LabelledGeneric 一般更新案例类字段?
【发布时间】:2016-04-12 17:40:59
【问题描述】:

使用 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)

我希望Test 类(和其他类)扩展一个抽象类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 返回&lt;notype&gt;。相当于:

scala> weakTypeOf[test.gen.Repr].baseType(weakTypeOf[::[_, _]].typeConstructor.typeSymbol)
res12: reflect.runtime.universe.Type = <notype>

这是使用 shapeless 2.3,尽管它在 2.2 中由于不同的原因而失败(似乎Updater 进行了很大的重构)。

有没有可能用 shapeless 来完成这个,还是我偏离了目标?

【问题讨论】:

    标签: scala shapeless


    【解决方案1】:

    这里的主要问题是 LabelledGeneric (Repr) 的优化结果类型丢失了。在Model,唯一知道Repr 的是Repr &lt;: HList。隐含的Updater.Aux[gen.Repr, F, gen.Repr] 搜索仅称为_ &lt;: HList 的东西,因此无法实现。 您必须使用两个类型参数定义 Model abstract class Model[A, L &lt;: 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。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-22
      • 2017-11-24
      • 2013-01-23
      • 2015-10-31
      • 2016-07-20
      • 1970-01-01
      • 2011-11-07
      • 2016-01-20
      相关资源
      最近更新 更多