I have this example code:
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)
}
Which is nice because I don't have to write the boilerplate that copies over all the fields from IncomingThing
to DatabaseIncomingThing
. However, I would love not having to maintain both those types since there is a very clear relationship between the two (one has id
, the other doesn't).
Is there a way to create a type from a given case class by either adding or removing one field? I imagine something like
type IncomingThing = withoutField[DatabaseIncomingThing]('id)
Or something to that effect.
Instead of DatabaseIncomingThing
val updated: DatabaseIncomingThing =
genDatabase.from(structuralVersionOfIncomingThing + ('id ->> UUID.randomUUID()))
you can work with raw 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
On type level
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]]
You can create your type 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
}
and use it
def foo[Out <: HList](implicit withoutField: WithoutField.Aux[DatabaseIncomingThing, Witness.`'id`.T, Out]) = {
// now you can use type Out inside
type IncomingThing = Out
???
}