Search code examples
scalashapeless

Shapeless: How to express <: case class type param for Generix.Aux


I've got the following code to basically iterate through the fields of a case class and map them using a Poly to the same type and use ToList[HL, Out]

For simplicity we can assume the Poly does this:

object Schema extends Poly1 {
  implicit def caseInt = at[Int](_ => "I'm an int")
  implicit def caseString = at[String](_ => "Oh boy a string")
}

def infer[V1 <: Product, Out <: HList, MapperOut <: HList](v1: V1)(
  implicit gen: Generic.Aux[V1, Out],
    map: Mapper.Aux[Schema.type, Out, MapperOut],
    to: ToList[MapperOut, String]
): List[String] = to (gen to v1 map Schema)

This is all very straightforward and works very well for simple scenarios:

case class Test(id: Int, text: String)
val list = infer(Test(2, "text"))
// List("I'm an int", "Oh boy a string")

Now going out to where the buses don't run:

class Automagical[T <: Product with Serializable : TypeTag] {
  def instance: T 
  // The typetag bit is needed for something else
  def convert: List[String] = infer(instance)
}

Sadly any call to above fails with:

could not find implicit value for parameter gen: shapeless.Generic.Aux[T,Out]

Bonus

How can I improve the infer method by not requiring an instance of T at all? Obviously type inference is fine, but I do need to somehow materialise a List[String] from an HList[Lub] and map over something.

Given I only ever care about the types, is it possible to derive a concrete instance of List[String] by only knowing the types to be poly mapped encoded as an HList?

Something like:

 def infer[V1 <: Product, Out <: HList, MapperOut <: HList]()(
  implicit gen: Generic.Aux[V1, Out],
    map: Mapper.Aux[Schema.type, Out, MapperOut],
    to: ToList[MapperOut, String]
): List[String] = {
  // Magically build an HList with samples from its types.
  // Or an alternative approach that gives the same outcome
  val reifiedInstance = reify[Out]
  to (reifiedInstance map Schema)
}

Solution

  • To ensure that convert has access to the implicit parameters for infer, they have to be present when the Automagical instance is created:

    abstract class Automagical[T <: Product with Serializable : TypeTag,
        Out <: HList, MapperOut <: HList]
      (implicit
        gen: Generic.Aux[T, Out],
        map: Mapper.Aux[Schema.type, Out, MapperOut],
        to: ToList[MapperOut, String]) {
      def instance: T
      // The typetag bit is needed for something else
      def convert: List[String] = infer(instance)
    }