Shapeless:找不到更新程序的隐式值

Shapeless: Could not find implicit value for updater

我正在做一个副项目,试图在 Scala 中实现不可变聚合。我的想法是让基本特征 AggregateRoot 具有一些常见的行为。子 classes 将是建模为案例 classes 的实际聚合。现在,有一件事我不喜欢,那就是我不能从基本特征调用 copy 方法,原因有两个:

  1. 我无法访问基本特征中的 copy 方法
  2. 我不知道 copy 方法会有多少参数

我对 shapeless 库有一些基本了解,我认为它可能对这种情况有所帮助。我的想法是将标记字段列表传递给特征中的基本方法,该方法将替换它们和 return 案例的新实例 class。 作为朝着这个方向迈出的一步,我正在尝试创建一种方法,该方法将使用 shapeless 开始复制一个字段,但我一直遇到相同的错误,即编译器无法找到更新程序的隐式。

这是我尝试使用的简化代码片段:

import shapeless._
import shapeless.labelled.FieldType
import shapeless.ops.record.Updater
import shapeless.record._
import shapeless.syntax.singleton._


trait AggregateRoot[T <: AggregateRoot[T]] {
  self: T =>

  def makeCopy[L <: HList, K, V](ft: FieldType[K, V])(
    implicit
    labeledGen: LabelledGeneric.Aux[T, L],
    updater: Updater.Aux[L, FieldType[K, V], L],
    witness: Witness.Aux[K]): T = {

    val labeledHList = labeledGen.to(this)
    val result = labeledHList.updated(witness, ft)
    labeledGen.from(result)
  }

}

case class User(id: String, age: Int) extends AggregateRoot[User]()

val user1 = User("123", 10)
val ageChange = "age" ->> 22
val user2 = user1.makeCopy(ageChange)

由于我不是经验丰富的用户,我不确定为什么它找不到请求的隐式。 shapeless 的版本是 2.3.3.

据我从这个很好的答案中了解到: - 在一般情况下你不能有 Updater,因为 Shapeless 需要为每个特定字段派生它以防万一 class,这意味着您需要为每个特定字段提供方法,而不是具有通用目的 makeCopy,例如:

import shapeless._, record._, ops.record._, labelled._, syntax.singleton._, tag._


trait AggregateRoot[T <: AggregateRoot[T]] {
  self: T =>
  import AggregateRoot._

  def withAge[L <: HList, K, V](age: Int)(implicit
    gen: LabelledGeneric.Aux[T, L],
    upd: Updater.Aux[L, F, L]
  ): T = {
    val ageField = AgeField(age)
    gen.from(upd(gen.to(this), ageField))
  }
}

object AggregateRoot {
  type AgeField = Symbol with Tagged[Witness.`"age"`.T]
  val  AgeField = field[AgeField]

  type F = FieldType[AgeField, Int]
}

import AggregateRoot._

case class User(id: String, age: Int) extends AggregateRoot[User]

object User {
  implicit val gen = LabelledGeneric[User]
}

val user1 = User("123", 10)
val ageChange = "age" ->> 22
val user2 = user1.withAge(ageChange)

val test = User("test-id", 20)
println("test.withAge(42) = " + test.withAge(20))
println("test.withAge(12).withAge(42) = " + test.withAge(12).withAge(42))

斯卡蒂:https://scastie.scala-lang.org/kDnL6HTQSEeSqVduW3EOnQ