如何使用LabelledGeneric一般更新案例类字段?

时间:2016-03-02 04:44:41

标签: scala 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的{​​{1}}以上代码更新id字段,如果它有一个(它应该)。

我的尝试将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]

调用test.withId(1)时,隐式Updater无法实现。宏报告gen.Repr不是HList类型,实际上是。{似乎this match是失败的,u baseType HConsSym返回<notype>。相当于:

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

这是使用无形2.3,虽然它在2.2中因为不同的原因而失败(似乎Updater有一个大的重构)。

是否有可能通过无形的方式实现这一目标,还是远离目标?

1 个答案:

答案 0 :(得分:4)

这里的主要问题是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。