【问题标题】:Create "enriched" type from case class with Shapeless in Scala在 Scala 中使用 Shapeless 从案例类创建“丰富”类型
【发布时间】:2019-03-04 03:08:43
【问题描述】:

我有这个示例代码:

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)

}

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

有没有办法通过添加或删除一个字段来从给定的案例类创建类型? 我想像

type IncomingThing = withoutField[DatabaseIncomingThing]('id)

或类似的东西。

【问题讨论】:

    标签: scala shapeless structural-typing


    【解决方案1】:

    而不是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]]
    

    你可以创建你的类型类

    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
      ???
    }
    

    【讨论】:

    • 是的——我想要一些可以被我的依赖项使用的东西,例如Play、Circe、Slick、Scalacheck 等。我认为这个命令可能太高了。我和我的同事正在讨论明确保留这两种类型以保持代码的意图清晰。我只是很好奇它是否可以完成。
    • 如果你使用LabelledGeneric,你的依赖项中应该有Shapeless。够了。
    • 如果你想自动生成案例类DatabaseIncomingThing,那么你需要一个宏。
    • 我再次更新了我的答案。你可以创建你的类型类WithoutField
    猜你喜欢
    • 2016-09-17
    • 1970-01-01
    • 1970-01-01
    • 2017-04-04
    • 2017-11-24
    • 1970-01-01
    • 2016-12-05
    • 1970-01-01
    • 2015-10-19
    相关资源
    最近更新 更多