In my second post I explained how shapeless can add, remove and align fields from case classes to match another type. The code works, but it’s pretty clunky. In this post I will clean it up a bit to make it less verbose
9-cleanup
tagAutomatically dropping unused fields
In the current implementation I need to manually drop the isDiesel
flag from the Vehicle
as it’s not needed
by the Reservation
case class. Shapeless can do this for us by comparing the expected fields and dropping anything
that is not used. The type class is called Intersection
and it’s usage is similar to the Align
type class we already
looked at. We will expand the ShapelessOps.genericAligner
to use the new class:
implicit def genericAligner[ARepr <: HList, Unaligned <: HList, B, BRepr <: HList](
implicit
bGen : LabelledGeneric.Aux[B, BRepr],
inter : Intersection.Aux[ARepr, BRepr, Unaligned], // NEW
align : Align[Unaligned, BRepr]
): Aligner[ARepr, B] = new Aligner[ARepr, B] {
def apply(a: ARepr): B = bGen.from(align(inter(a)))
}
Intersection.Aux[ARepr, BRepr, Unaligned]
basically means
take an intersection of ARepr and BRepr and expose a new type as Unaligned
We can then summon an Align instance to align Unaligned to BRepr. It’s a bit messy but Shapeless is about pushing the boundaries of the compiler. Some may even say we’re cheating the compiler!
Cleanup the code
We can also add a new implicit class to avoid the need for repeated LabelledGeneric[X]
calls:
implicit class GenericOps[A, ARepr <: HList](val a: A)(implicit gen: LabelledGeneric.Aux[A, ARepr]) {
def repr: ARepr = LabelledGeneric[A].to(a)
}
Our final code now looks much cleaner:
object Main {
import ShapelessOps._
import shapeless.syntax.singleton._
def main(args: Array[String]): Unit = {
val location = Location(pickup = "Malaga Airport", dropOff = "Malaga Airport", from = LocalDate.of(2018,8,1), to = LocalDate.of(2018,8,10))
val vehicle = Vehicle(vehicleCategory = "Economy", automatic = false, numDoors = 4, isDiesel = true)
val driver = Driver(driverAge = 35, nationality = "British")
val misalignedReservationRepr = location.repr ++ vehicle.repr ++ driver.repr :+ ('confirmed ->> true)
/*_*/ val reservation = misalignedReservationRepr.as[Reservation] /*_*/
println(s"from date: ${reservation.from}")
}
}