First of all never define trait
members as val
if they are meant to be implemented at a later point.
trait UberSomething {
def name: String
}
// these maybe in different files
case class Something(name: String) extends UberSomething
case class SomethingOther(name: String) extends UberSomething
import shapeless._, ops.hlist.Align
Another approach I've seen somewhere on Stackoverflow before, so apologies for stealing street cred, is to use Align
such that order of the fields wouldn't matter.
class Convert[Target] {
def apply[Source, HLS <: HList, HLT <: HList](s: Source)(implicit
// Convert the Source to an HList type
// include field names, e.g "labelled"
// Shapeless "generates" this using an implicit macro
// it looks at our type, extracts a list of (Name, Type) pairs
genS: LabelledGeneric.Aux[Source, HLS],
// Convert the Target o an HList type
// include field names, e.g "labelled"
// So again we have a (Name, Type) list of pairs this time for Target
genT: LabelledGeneric.Aux[Target, HLT],
// Use an implicit align to make sure the two HLists
// contain the same set of (Name, Type) pairs in arbitrary order.
align: Align[HLS, HLT]
) = genT from align(genS to s)
}
// Small trick to guarantee conversion only requires
// a single type argument, otherwise we'd have to put something
// in place for HLS and HLT, which are meant to be path dependant
// and "calculated" by the LabelledGeneric.Repr macro so it wouldn't work as it breaches the "Aux pattern", which exposes a type member materialized by a macro in this case.
// HLT and HLS come from within genS.Repr and genT.Repr.
def convert[T] = new Convert[T]
This is a bit better as the HList
params are nicely masked as part of apply
so you don't trip yourself up.
val sample = Something("bla")
convert[SomethingOther](sample) // SomethingOther("bla")
Let's review this line: genT from align(genS to s)
.
First genS to s
converts the Source
instance to a LabelledGeneric
, e.g an HList
with field info.
Align aligns the types and fields of the created HList
for the Source
type to match the Target
type.
genT from ..
allows us to create an instance of Target
from an HList
granted the compiler can "prove" the fields and types are "all there", which is something we already have with Align
.