无形:找不到更新程序的隐含值

问题描述

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

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

我对无形库有一些基本的了解,我认为在这种情况下它可能会有所帮助。我的想法是将标记字段列表传递给特征中的基本方法,该方法将替换它们并返回案例类的新实例。 作为朝这个方向迈出的一步,我正在尝试创建一个方法,该方法将使用 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],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。

解决方法

据我从这个很好的答案中了解到:How to generically update a case class field using LabelledGeneric? - 在一般情况下你不能有 Updater,因为 Shapeless 需要为 case 类中的每个特定字段派生它,这意味着而不是具有通用目的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