我有一个方法采用 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)"
}
}
但是typechecker爆炸了:
宏扩展期间的异常:scala.reflect.macros.TypecheckException:推断类型参数[Int,Boolean]不符合方法apply的类型参数bounds [H,T <:shapeless.HList]
显然,编译器试图在宏扩展之前从块中推断 [Int, Boolean]
. 我也不明白为什么它需要两个参数,其中 describe
和 MyThing
只需要一个参数 .
有没有办法让由宏生成的树驱动类型推断?
2 回答
我支持自动翻译,如果你想在你的项目中使用
-Xlint
,他的答案中的解决方案会引起很多警告噪音 . 我绝对同意你应该避免在只提供语法糖的情况下使用宏,我会选择宏 .在你的情况下,这不是太难 - 你的逻辑中只有一个小错误(好吧,两个,真的) . 以下将工作得很好:
或者更简洁:
最大的问题就在于减少 - 你需要从
HNil
开始,而不是 Build 一个毫无意义的中间事物,然后再加以解决 . 您还需要捕获块的表达式,并将其键入Any
而不是Unit
以避免丢弃值 .(作为旁注,我有点惊讶它作为白盒宏工作,但从2.11.2开始 . )
我个人更喜欢这种语法和逗号,这也很简单:
此处的用法与产品解决方案相同,但不涉及自动翻译 .
如果你可以使用逗号分隔的参数列表,那么你可以遵循shapeless的
HList
伴随对象apply
方法中使用的样式,一般来说,如果存在可行的非宏替代方案,我的建议是避免使用宏 .