I have the following type definitions:
trait Content
trait Wrapper {
type ContentType
}
final case class Foo(param: String) extends Content
final case class Bar(param: String) extends Content
final case class FooWrapper(foo: Foo) extends Wrapper { type ContentType = Foo }
final case class BarWrapper(bar: Bar) extends Wrapper { type ContentType = Bar }
Given a content value at runtime, I would like to return it wrapped in its corresponding wrapper type. I tried the following using Shapeless:
def fetchWrapper[N, G <: Wrapper](
implicit
gen: Generic.Aux[G, N :: HNil],
// this also compiles, as an alternative to Generics.Aux
// =:= :G#ValueType =:= N
) = ...
It works, but only if I explicitly provide the type parameters: fetchWrapper[Foo, FooWrapper]
. How do I take advantage of implicit resolution to generalize things so that I can derived the correct wrapper for a given content?
I was thinking of generating an instance of the wrapper using the same derivation technique in the random number generator section of the shapeless book (i.e a typelcass that produces BarWrapper
if I have an implicit Bar :: HNil
in scope), but I can't even find the correct wrapper type in the first place.
Generic
can easily help with transforming Wrapper
subtype into Content
subtype but you want vice versa.
Try a type class
trait ContentToWrapper[C <: Content] {
type Out <: Wrapper { type ContentType = C }
}
object ContentToWrapper {
implicit val foo: ContentToWrapper[Foo] { type Out = FooWrapper } = null
implicit val bar: ContentToWrapper[Bar] { type Out = BarWrapper } = null
}
def fetchWrapper[C <: Content](implicit ctw: ContentToWrapper[C]): ctw.Out = ???
If you make Wrapper
sealed you can derive the type class
import shapeless.{Coproduct, Generic, HList, Poly1, poly}
import shapeless.ops.coproduct.ToHList
import shapeless.ops.hlist.CollectFirst
object ContentToWrapper {
implicit def mkContentToWrapper[C <: Content, WC <: Coproduct, WL <: HList](implicit
generic: Generic.Aux[Wrapper, WC],
toHList: ToHList.Aux[WC, WL], // there is CollectFirst for HList but not for Coproduct
collect: CollectFirst[WL, WrapperSubtypePoly[C]]
): ContentToWrapper[C] { type Out = collect.Out } = null
trait WrapperSubtypePoly[C] extends Poly1
object WrapperSubtypePoly {
implicit def cse[C, A <: Wrapper { type ContentType = C }]:
poly.Case1.Aux[WrapperSubtypePoly[C], A, A] = poly.Case1(identity)
}
}
Testing:
val w1 = fetchWrapper[Foo]
w1: FooWrapper
val w2 = fetchWrapper[Bar]
w2: BarWrapper