我有一个方法,使用HList
并使用它来构建类的实例。我想提供一些简化的语法,隐藏显式的缺点。所以我想从:
MyThingy.describe( 42 :: true :: "string" :: HNil)
MyThingy.describe {
42
true
"string"
}
其中MyThingy
定义为
class MyThingy[L <: HList](elems: L)
我已经尝试了这个宏
def describe[L <: HList](elements: Unit): MyThingy[L] = macro MyThingyMacros.describeImpl[L]
和
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)"
}
}
但是类型检查器爆炸了:
宏展开时出现异常:scala.reflect.macros.TypecheckException:推断的类型参数[Int,Boolean]不符合方法apply的类型参数界限[H,T <: shapeless]。HList]
显然,编译器试图从宏扩展之前的块中推断[Int, Boolean]
。我也不明白为什么它需要两个参数,而describe
和MyThing
只需要一个参数。
是否有一种方法可以由宏生成的树驱动类型推断?
如果您可以接受逗号分隔的参数列表,那么您可以遵循shapeless的HList
伴侣对象apply
方法中使用的样式,
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
一般来说,如果有可行的非宏替代方法,我建议避免使用宏。
在这一点上,我要恭敬地不同意Miles的观点。我个人不能忍受自动耦合,如果你想在你的项目中使用-Xlint
,他的答案中的解决方案将会引起很多警告噪音。我绝对同意,当有可行的替代方法时,应该避免使用宏,但如果我必须在自动耦合和宏之间做出选择,而我只是在提供语法糖的情况下,我会选择宏。
在你的情况下,这并不难——只是在你的逻辑中有一个小错误(好吧,实际上是两个)。下面的代码可以正常工作:
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]
或者更简洁地说:
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!")
}
}
最大的问题就在简化中——你需要从HNil
开始,而不是建立一个无意义的中间东西,然后把它加上去。您还需要捕获块的表达式,并将其键入Any
而不是Unit
,以避免值丢弃。
(作为旁注,我有点惊讶它可以作为一个白盒宏,但从2.11.2开始它就可以了)
我个人更喜欢这种带逗号的语法,这也很简单:
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]
此处的用法与产品解决方案相同,但不涉及自动双元化。