Case 类 - 使用转换复制多个字段

Case classes - Copy multiple fields with transforms

我有一个包含 25 个字段的案例 class,需要将其转换为另一个包含 22 个字段的案例,其中 19 个是共享的,另外 3 个只是重命名。

我找到了一些如何使用 shapeless(例如 an answer here and some code examples from Miles Sabin here and here)执行此操作的示例,但最后一个看起来有点过时了,我无法从Github 示例如何使用 shapeless 重命名多个字段,或者在将字段添加到新对象之前对字段进行更多操作。谁能帮帮我?

简化的代码示例;

import shapeless.LabelledGeneric
case class A(fieldA:Int, fieldB:String, fieldC:String)
case class B(fieldARenamed:Int, fieldB:String, fieldC:String, fieldCTransformed:String)

val aGen = LabelledGeneric[A]
val bGen = LabelledGeneric[B]

val freddie = new A(1,"Freddie","somestring")

val record = aGen.to(freddie)
val atmp = freddie.fieldA
record.Remove("fielda")

val freddieB = bGen.from(record + 
  (Symbol("fieldARenamed") ->> atmp) +
  (Symbol("fieldCTransformed") ->> freddie.fieldC.toUpperCase)
) //Errors everywhere, even if I replace + with :: etc.

我有一种感觉 Align 会出现在这里的某个地方,但了解如何以最精简的方式做到这一点 - 例如如果不创建像 Field 这样的额外特征,就像上面第三个 link 那样 - 会很有趣。

The Shapeless Guide中也有一些单引号的用法,(例如'fieldC)表示法,我没能找到太多相关信息,所以如果它起到角色一些解释也将非常有帮助。对于 Scala 魔法的这种深度来说,这是相当新的,如果问题看起来很迟钝或涉及太多不同的主题,我们深表歉意。

编辑:为避免疑义,我 不是 寻找建议我只是通过引用第一个字段手动创建新案例 class 的答案, 如;

val freddieB = B(fieldARenamed = freddie.fieldA, fieldB = freddie.fieldB, fieldC = freddie.fieldC, fieldCTransformed =freddie.fieldC.toUpperCase)

请参阅下面的评论,了解为什么这是不合适的。

最简单的解决方案是使用旧实例的值构造新实例 case class,并根据需要对这些值应用函数。代码将非常高效,代码的目的将非常明确,编写它比任何其他解决方案花费的时间都更少,它比依赖于 third-party 库的解决方案更健壮和可维护,并且它避免了两者之间隐藏的依赖关系 类.

另一种选择是使用 automapper; in particular, Dynamic Mappings 功能。

对于您的特定示例,它看起来如下所示:

import io.bfil.automapper._

case class A(fieldA:Int, fieldB:String, fieldC:String)
case class B(fieldARenamed:Int, fieldB:String, fieldC:String, fieldCTransformed:String)

val freddie = new A(1,"Freddie","somestring")

val freddieB = automap(freddie).dynamicallyTo[B](
  fieldARenamed = freddie.fieldA, 
  fieldCTransformed = freddie.fieldC.toUpperCase
)

我想你可以把它变成一个函数

def atob(a: A): B = {
  automap(a).dynamicallyTo[B](
    fieldARenamed = a.fieldA, 
    fieldCTransformed = a.fieldC.toUpperCase
  )
}

从效率的角度来看,这个库使用了宏,所以generated code几乎和手写的一样好

仅供参考,这是让您的问题代码起作用的一种方法。

import shapeless._
import shapeless.labelled.FieldType
import shapeless.ops.hlist.{Align,Intersection}
import shapeless.syntax.singleton._

case class A(fieldA:Int, fieldB:String, fieldC:String)
case class B(fieldARenamed:Int, fieldB:String, fieldC:String, fieldCTransformed:String)

val fromGen = LabelledGeneric[A]
val toGen   = LabelledGeneric[B]

val freddie = A(1, "Freddie", "somestring")
val putARename = Symbol("fieldARenamed")     ->> freddie.fieldA
val putCTrans  = Symbol("fieldCTransformed") ->> freddie.fieldC.toUpperCase

trait Field { type K; type V; type F = FieldType[K, V] }
object Field {
  def apply[K0,V0](sample: FieldType[K0,V0]) =
    new Field { type K = K0; type V = V0 }
}

val pFieldA  = Field(putARename)
val pFieldCT = Field(putCTrans)

val inter = Intersection[pFieldA.F :: pFieldCT.F :: fromGen.Repr, toGen.Repr]
val align = Align[inter.Out, toGen.Repr]

toGen.from(align(inter(putARename :: putCTrans :: fromGen.to(freddie))))
//res0: B = B(1,Freddie,somestring,SOMESTRING)