Infer HList type when creating a list using a macro

I have a method that takes an HList and uses it to instantiate a class. I would like to provide some simplified syntax while hiding explicit cons. Therefore, I would like to go from:

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

to

 MyThingy.describe { 42 true "string" } 

where MyThingy is defined as

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

I tried 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 typechecker explodes:

exception when expanding a macro: scala.reflect.macros.TypecheckException: the arguments inferred type [Int, Boolean] do not match the method applied to the parameters of the parameters of the type [H, T <: shapeless.HList]

Apparently, the compiler is trying to infer [Int, Boolean] from the block before expanding the macro. I also do not understand why this requires two parameters, where describe and MyThing require only one.

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

+6
source share
2 answers

I'm going to respectfully disagree with Miles here. I personally can’t tolerate automatic drag and drop, and if you want to use -Xlint in your project, the solution in his answer will cause a lot of warning noise. I definitely agree that you should avoid macros when there is a viable alternative, but if I had to choose between auto-tupling and a macro in the case where I just provide syntactic sugar, I would go with the macro.

In your case, this is not too complicated - there is only a minor error (well, two, really) in your logic. The following will work just fine:

 import scala.language.experimental.macros import scala.reflect.macros.whitebox.Context import shapeless._ class MyThingy[L <: HList](val elems: L) def describeImpl[L <: HList: c.WeakTypeTag](c: Context)(elems: c.Tree) = { import c.universe._ def concatHList: PartialFunction[Tree, Tree] = { case Block(statements, last) => statements.foldRight(q"$last :: shapeless.HNil")( (h, t) => q"shapeless.::($h, $t)" ) } 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)" } } def describe[L <: HList](elems: Any): MyThingy[L] = macro describeImpl[L] 

Or more briefly:

 def describeImpl[L <: HList: c.WeakTypeTag](c: Context)(elems: c.Tree) = { import c.universe._ elems match { case q"{ ..$elems }" => val hlist = elems.foldRight[c.Tree](q"shapeless.HNil: shapeless.HNil")( (h, t) => q"shapeless.::($h, $t)" ) q"new MyThingy($hlist)" case _ => c.abort(c.enclosingPosition, "BOOM!") } } 

The biggest problem was only to reduce it - you need to start with HNil , and not create a meaningless intermediate thing, and then apply it. You also need to grab the block expression and enter it as Any instead of Unit to avoid deviating the value.

(As a side note, I'm a little surprised that this works like a white box macro, but as of 2.11.2.)

I personally prefer this syntax with commas, and it is also pretty easy:

 def describeImpl[L <: HList: c.WeakTypeTag](c: Context)(elems: c.Tree*) = { import c.universe._ val hlist = elems.foldRight[c.Tree](q"shapeless.HNil: shapeless.HNil")( (h, t) => q"shapeless.::($h, $t)" ) q"new MyThingy($hlist)" } def describe[L <: HList](elems: Any*): MyThingy[L] = macro describeImpl[L] 

The use here is the same as with the solution of the product, but there is no automatic tupling involved.

+4
source

If you can live with a comma-separated list of arguments, you can follow the style used in the formless 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 alternative without macros.

+7
source

Source: https://habr.com/ru/post/974270/


All Articles