scalashapelessscala-macroshlist

Get sequence of types from HList in macro


Context: I'm trying to write a macro that is statically aware of an non-fixed number of types. I'm trying to pass these types as a single type parameter using an HList. It would be called as m[ConcreteType1 :: ConcreteType2 :: ... :: HNil](). The macro then builds a match statement which requires some implicits to be found at compile time, a bit like how a json serialiser might demand implicit encoders. I've got a working implementation of the macro when used on a fixed number of type parameters, as follows:

def m[T1, T2](): Int = macro mImpl[T1, T2]

def mImpl[T1: c.WeakTypeTag, T2: c.WeakTypeTag](c: Context)(): c.Expr[Int] = {
  import c.universe._
  val t = Seq(
    weakTypeOf[T1],
    weakTypeOf[T2]
  ).map(c => cq"a: $c => externalGenericCallRequiringImplicitsAndReturningInt(a)")
  val cases = q"input match { case ..$t }"
  c.Expr[Int](cases)
}

Question: If I have a WeakTypeTag[T] for some T <: HList, is there any way to turn that into a Seq[Type]?

def hlistToSeq[T <: HList](hlistType: WeakTypeTag[T]): Seq[Type] = ???

My instinct is to write a recursive match which turns each T <: HList into either H :: T or HNil, but I don't think that kind of matching exists in scala.

I'd like to hear of any other way to get a list of arbitrary size of types into a macro, bearing in mind that I would need a Seq[Type], not Expr[Seq[Type]], as I need to map over them in macro code.

A way of writing a similar 'macro' in Dotty would be interesting too - I'm hoping it'll be simpler there, but haven't fully investigated yet.

Edit (clarification): The reason I'm using a macro is that I want a user of the library I'm writing to provide a collection of types (perhaps in the form of an HList), which the library can iterate over and expect implicits relating to. I say library, but it will be compiled together with the uses, in order for the macros to run; in any case it should be reusable with different collections of types. It's a bit confusing, but I think I've worked this bit out - I just need to be able to build macros that can operate on lists of types.


Solution

  • Currently you seem not to need macros. It seems type classes or shapeless.Poly can be enough.

    def externalGenericCallRequiringImplicitsAndReturningInt[C](a: C)(implicit 
      mtc: MyTypeclass[C]): Int = mtc.anInt
    
    trait MyTypeclass[C] {
      def anInt: Int
    }
    object MyTypeclass {
      implicit val mtc1: MyTypeclass[ConcreteType1] = new MyTypeclass[ConcreteType1] {
        override val anInt: Int = 1
      }
    
      implicit val mtc2: MyTypeclass[ConcreteType2] = new MyTypeclass[ConcreteType2] {
        override val anInt: Int = 2
      }
    
      //...
    }
    
    val a1: ConcreteType1 = null
    val a2: ConcreteType2 = null
    externalGenericCallRequiringImplicitsAndReturningInt(a1) //1
    externalGenericCallRequiringImplicitsAndReturningInt(a2) //2