Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Infer HList type when building the list with a macro

I have a method taking an HList and using it to build an instance of a class. I would like to provide some simplified syntax, hiding the explicit cons. So I'd like to go from:

MyThingy.describe( 42 :: true :: "string" :: HNil)

to

MyThingy.describe {
  42
  true
  "string"
}

where MyThingy is defined like

class MyThingy[L <: HList](elems: L)

I've made an attempt with this macro

def describe[L <: HList](elements: Unit): MyThingy[L] = macro MyThingyMacros.describeImpl[L]

and

def describeImpl[L <: shapeless.HList : c.WeakTypeTag](c: Context)(elems: c.Tree): c.Tree = {
  import c.universe._

  def concatHList: PartialFunction[Tree, Tree] = {
    case Block(l, _) =>
      val els = l.reduceRight((x, y) => q"shapeless.::($x,$y)")
      q"$els :: shapeless.HNil"
  }

  concatHList.lift(elems) match {
    case None => c.abort(c.enclosingPosition, "BOOM!")
    case Some(elemsHList) =>
      val tpe = c.typecheck(elemsHList).tpe
      q"new MyThingy[$tpe]($elemsHList)"
  }

}

but the typechecker explodes:

exception during macro expansion: scala.reflect.macros.TypecheckException: inferred type arguments [Int,Boolean] do not conform to method apply's type parameter bounds [H,T <: shapeless.HList]

Apparently the compiler is trying to infer [Int, Boolean] from the block before the macro expansion. I also don't understand why it requires two parameters, where describe and MyThing only require one.

Is there a way to have type inference driven by the tree produced by the macro?

like image 243
Gabriele Petronella Avatar asked Aug 24 '14 17:08

Gabriele Petronella


1 Answers

If you can live with a comma separated argument list then you could follow the style used in shapeless's HList companion object apply method,

scala> import shapeless._
import shapeless._

scala> object MyThingy {
     |   def describe[P <: Product, L <: HList](p : P)
     |     (implicit gen: Generic.Aux[P, L]) : L = gen.to(p)
     | }
defined object MyThingy

scala> MyThingy.describe(42, true, "String")
res0: this.Repr = 42 :: true :: String :: HNil

scala> res0.head
res1: Int = 42

In general my recommendation is to avoid macros if there is a viable non-macro alternative.

like image 185
Miles Sabin Avatar answered Nov 09 '22 16:11

Miles Sabin