Search code examples
scalagenericsshapeless

Search for types based on their abstract type members


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.


Solution

  • 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