Massaging case classes with shapeless
Agenda
- Context - DDD
 - HList
 - Generic
 - LabelledGeneric
 - More complex transformations
 - Chimney
 
DDD in one slide
Domain
Translation
HTTP API
Database
Translation






List
val xs: List[Int] = List(1, 2, 3)HList
val hlist: String :: Int :: Boolean :: HNil = 
           "yeah" :: 123 :: true    :: HNilGeneric
"yeah" :: 123 :: true :: HNilcase class DomainModel(
      a: String, b: Int, c: Boolean)case class ApiRepresentation(
      x: String, y: Int, z: Boolean)Generic
scala> import shapeless._
import shapeless._
scala> val model = DomainModel("yeah", 123, true)
model: DomainModel = DomainModel(yeah,123,true)
scala> val hlist = Generic[DomainModel].to(model)
hlist: String :: Int :: Boolean :: shapeless.HNil = 
    yeah :: 123 :: true :: HNil
scala> val apiRepr = 
     | Generic[ApiRepresentation].from(hlist)
apiRepr: ApiRepresentation = 
    ApiRepresentation(yeah,123,true)LabelledGeneric
("a" ->> "yeah") :: ("b" ->> 123) :: ("c" ->> true) :: HNilcase class DomainModel(
      a: String, b: Int, c: Boolean)case class ApiRepresentation(
      x: String, y: Int, z: Boolean)LabelledGeneric
scala> LabelledGeneric[DomainModel].to(model)
res0: 
  String with shapeless.labelled.KeyTag[
    Symbol with shapeless.tag.Tagged[String("a")],
    String
  ] :: 
  Int with shapeless.labelled.KeyTag[
    Symbol with shapeless.tag.Tagged[String("b")],
    Int
  ] :: 
  Boolean with shapeless.labelled.KeyTag[
    Symbol with shapeless.tag.Tagged[String("c")],
    Boolean
  ] :: 
  shapeless.HNil = 
  yeah :: 123 :: true :: HNil... crazy type signature ...
LabelledGeneric
scala> LabelledGeneric[DomainModel].to(model)
res0: 
  String with shapeless.labelled.KeyTag[
    Symbol with shapeless.tag.Tagged[String("a")],
    String
  ] :: 
  Int with shapeless.labelled.KeyTag[
    Symbol with shapeless.tag.Tagged[String("b")],
    Int
  ] :: 
  Boolean with shapeless.labelled.KeyTag[
    Symbol with shapeless.tag.Tagged[String("c")],
    Boolean
  ] :: 
  shapeless.HNil = 
  yeah :: 123 :: true :: HNilMore complex transformations
- Removing fields
 - Types don't exactly match
 
Removing fields
case class FiveFields(a: String, 
                      b: Int, 
                      c: Boolean,
                      d: String,
                      e: Double)case class FourFields(a: String, 
                      b: Int, 
                      c: Boolean,
                      e: Double)("a"->>"yep")::("b"->>1)::("c"->>true)::("d"->>"wow")::("e"->>4.5)::HNil("a"->>"yep")::("b"->>1)::("c"->>true)::               ("e"->>4.5)::HNilRemoving fields
scala> val fiveFields =
     | FiveFields("yeah", 123, true, "wow", 4.5)
fiveFields: ... = FiveFields(yeah,123,true,wow,4.5)
scala> val fiveFieldsHList = 
     | LabelledGeneric[FiveFields].to(fiveFields)
fiveFieldsHList: ... 
  = yeah :: 123 :: true :: wow :: 4.5 :: HNil
scala> import shapeless.record._
import shapeless.record._
scala> val fourFieldsHList = fiveFieldsHList - 'd
fourFieldsHList: ... 
  = yeah :: 123 :: true :: 4.5 :: HNilTypes don't match
case class DomainModel(a: String, 
                       b: Int, 
                       c: Boolean)case class ApiRepr(a: String, 
                   b: Option[Int], 
                   c: Option[Boolean])("a"->>"yep") :: ("b"->>1)       :: ("c"->>true)       :: HNil("a"->>"yep") :: ("b"->>Some(1)) :: ("c"->>Some(true)) :: HNilTypes don't match
String ::     Int     ::     Boolean     :: HNilString :: Option[Int] :: Option[Boolean] :: HNil(do nothing)
(wrap in Some)
Polymorphic function
object WrapWithOptionIfNecessary extends Poly1 {
  // Reflexively convert any field to itself
  // by doing nothing
  implicit def refl[A]: Case.Aux[A, A] =
    at[A](Predef.identity)
  // Convert a field `x: A` into a field `x: Option[A]` 
  // by wrapping it in Some()
  implicit def wrapWithOption[K, V]: 
      Case.Aux[FieldType[K, V], FieldType[K, Option[V]]] =
    at[FieldType[K, V]](x => field[K](Some(x)))
}
Massage
/**
  * Massages an HList of type `From` 
  * into an HList of type `To`
  * using `WrapWithOptionIfNecessary`
  * to convert elements appropriately.
  */
trait Massage[From <: HList, To <: HList] {
 
  def apply(from: From): To
}
Inductive proof
\forall n \in \mathbb{N} .f(n) 
∀n∈N.f(n)
If we can prove the base case:
f(0)
f(0)
and the inductive step:
\forall n \in \mathbb{N} .f(n) \Rightarrow f(n+1) 
∀n∈N.f(n)⇒f(n+1)
then we have proved it for all natural numbers:
Inductive proof
If we can prove the base case:
and the inductive step:
then we have proved it for some type From <: HList
there is a Massage instance for HNil
if there is a Massage instance for Tail
and we can convert Head
then there is a Massage instance for Head :: Tail
Chimney
Chimney
import io.scalaland.chimney._
implicit def wrapWithOption[A]: Transformer[A, Option[A]] =
  new Transformer[A, Option[A]] {
    def transform(a: A): Option[A] = Some(a)
  }Chimney
import io.scalaland.chimney.dsl._
val domainModel = DomainModel("yeah", 123, true)
val apiRepr = domainModel.into[ApiRepr].transform
Questions?
Massaging case classes with shapeless
By Chris Birchall
Massaging case classes with shapeless
- 3,392