在 Scala 中使用 Shapeless 从 case class 创建 "enriched" 类型

Create "enriched" type from case class with Shapeless in Scala

我有这个示例代码:

import java.util.UUID

import shapeless.LabelledGeneric
import shapeless.record._
import shapeless.syntax.singleton._

object LabelTest extends App {

  case class IncomingThing(name: String, age: Int)
  case class DatabaseIncomingThing(name: String, age: Int, id: UUID)

  val genIncoming = LabelledGeneric[IncomingThing]
  val genDatabase = LabelledGeneric[DatabaseIncomingThing]

  val thing = IncomingThing("John", 42)

  val structuralVersionOfIncomingThing = genIncoming.to(thing)

  val updated = genDatabase.from(structuralVersionOfIncomingThing + ('id ->> UUID.randomUUID()))

  println(updated) // DatabaseIncomingThing(John,42,a45081f2-4ed5-4d2b-8fd9-4d8986875ed7)

}

这很好,因为我不必编写样板来复制从 IncomingThingDatabaseIncomingThing 的所有字段。但是,我希望不必同时维护这两种类型,因为两者之间存在非常明确的关系(一个有 id,另一个没有)。

有没有一种方法可以通过添加或删除一个字段从给定案例 class 创建类型? 我想象像

type IncomingThing = withoutField[DatabaseIncomingThing]('id)

或类似的东西。

而不是DatabaseIncomingThing

val updated: DatabaseIncomingThing = 
  genDatabase.from(structuralVersionOfIncomingThing + ('id ->> UUID.randomUUID()))

您可以使用原始 HList

val updated1: FieldType[Witness.`'name`.T, String] :: FieldType[Witness.`'age`.T, Int] :: FieldType[Witness.`'id`.T, UUID] :: HNil = 
  structuralVersionOfIncomingThing + ('id ->> UUID.randomUUID())
val updated2: FieldType[Witness.`'name`.T, String] :: FieldType[Witness.`'age`.T, Int] :: HNil = 
  updated1 - 'id

类型级别

implicitly[Remover.Aux[FieldType[Witness.`'name`.T, String] :: FieldType[Witness.`'age`.T, Int] :: FieldType[Witness.`'id`.T, UUID] :: HNil,
  Witness.`'id`.T,
  (UUID, FieldType[Witness.`'name`.T, String] :: FieldType[Witness.`'age`.T, Int] :: HNil)]]

implicitly[Updater.Aux[
  FieldType[Witness.`'name`.T, String] :: FieldType[Witness.`'age`.T, Int] :: HNil,
  FieldType[Witness.`'id`.T, UUID],
  FieldType[Witness.`'name`.T, String] :: FieldType[Witness.`'age`.T, Int] :: FieldType[Witness.`'id`.T, UUID] :: HNil]]

您可以创建自己的类型class

trait WithoutField[A, K] {
  type Out <: HList
}

object WithoutField {
  type Aux[A, K, Out0 <: HList] = WithoutField[A, K] { type Out = Out0 }
  def instance[A, K, Out0 <: HList]: Aux[A, K, Out0] = new WithoutField[A, K] { type Out = Out0 }

  implicit def mkWithoutField[A, L <: HList, K, T, L1 <: HList](implicit
    labelledGeneric: LabelledGeneric.Aux[A, L],
    remover: Remover.Aux[L, K, (T, L1)]): Aux[A, K, L1] = instance
}

并使用它

def foo[Out <: HList](implicit withoutField: WithoutField.Aux[DatabaseIncomingThing, Witness.`'id`.T, Out]) = {
  // now you can use type Out inside
  type IncomingThing = Out
  ???
}