You would want to take a look into Shapeless.
import shapeless._
import shapeless.syntax._
case class Cat(color: Int, isFat: Boolean)
case class Kitten(color: Int, isFat: Boolean)
val kitten = Kitten(2, true)
val genCat = Generic[Cat]
val genKit = Generic[Kitten]
val cat: Cat = genCat.from(genKit.to(kitten))
println(cat)
Shapeless is a library for generic programming. For example, the Generic typeclass can convert instances of any hierarchy of sealed traits and case classes (such as Cat
and Kitten
) into a generic representation (HLists and Coproducts), and back into any compatible class hierarchy. The generic representation in between can be manipulated in a type safe manner.
genKit.to(kitten)
takes a Kitten
, and produces a HList 2 :: true :: HNil
. Since that is compatible with the generic representation of Cat
without modification, it can be stored as such using genCat.from
.
In this case, Cat
and Kitten
are nearly identical. What if the order of types was different, or Kitten had an extra property? What if the name of each property is significant? There is tons of useful stuff in Shapeless to easily solve exactly these kind of situations by manipulating the generic representation. Take a look at this example, where some type Book is converted using LabelledGeneric (which uses a HLists with labels, a.k.a. Records), a property is added, and stored into ExtendedBook. All of this type-safe.
Shapeless does use some macros, but seemingly relies only on a minimal set of them. The user of Shapeless does not write any macros him/herself - the heavy lifting is done by Scala's powerful type system.