Gab*_*lla 6 macros scala type-inference shapeless
我有一个方法,HList并使用它来构建一个类的实例.我想提供一些简化的语法,隐藏明确的缺点.所以我想从:
MyThingy.describe( 42 :: true :: "string" :: HNil)
Run Code Online (Sandbox Code Playgroud)
至
MyThingy.describe {
42
true
"string"
}
Run Code Online (Sandbox Code Playgroud)
在哪里MyThingy被定义为
class MyThingy[L <: HList](elems: L)
Run Code Online (Sandbox Code Playgroud)
我试过这个宏
def describe[L <: HList](elements: Unit): MyThingy[L] = macro MyThingyMacros.describeImpl[L]
Run Code Online (Sandbox Code Playgroud)
和
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)"
}
}
Run Code Online (Sandbox Code Playgroud)
但是typechecker爆炸了:
宏扩展期间的异常:scala.reflect.macros.TypecheckException:推断类型参数[Int,Boolean]不符合方法apply的类型参数bounds [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
Run Code Online (Sandbox Code Playgroud)
一般来说,如果存在可行的非宏替代方案,我的建议是避免使用宏.
我在此谨表示不同意迈尔斯的观点。我个人无法忍受自动调整,如果你想-Xlint在你的项目中使用,他的答案中的解决方案会引起很多警告噪音。我绝对同意,当有可行的替代方案时,您应该避免使用宏,但是如果我必须在自动调整和宏之间进行选择,而我只是提供语法糖,那么我会选择宏。
在你的情况下,这并不太难\xe2\x80\x94,你的逻辑中只有一个小错误(嗯,两个,真的)。以下内容将正常工作:
\n\nimport scala.language.experimental.macros\nimport scala.reflect.macros.whitebox.Context\nimport shapeless._\n\nclass MyThingy[L <: HList](val elems: L)\n\ndef describeImpl[L <: HList: c.WeakTypeTag](c: Context)(elems: c.Tree) = {\n import c.universe._\n\n def concatHList: PartialFunction[Tree, Tree] = {\n case Block(statements, last) =>\n statements.foldRight(q"$last :: shapeless.HNil")(\n (h, t) => q"shapeless.::($h, $t)"\n )\n }\n\n concatHList.lift(elems) match {\n case None => c.abort(c.enclosingPosition, "BOOM!")\n case Some(elemsHList) =>\n val tpe = c.typecheck(elemsHList).tpe\n q"new MyThingy[$tpe]($elemsHList)"\n }\n}\n\ndef describe[L <: HList](elems: Any): MyThingy[L] = macro describeImpl[L]\nRun Code Online (Sandbox Code Playgroud)\n\n或者更简洁地说:
\n\ndef describeImpl[L <: HList: c.WeakTypeTag](c: Context)(elems: c.Tree) = {\n import c.universe._\n\n elems match {\n case q"{ ..$elems }" =>\n val hlist = elems.foldRight[c.Tree](q"shapeless.HNil: shapeless.HNil")(\n (h, t) => q"shapeless.::($h, $t)"\n )\n q"new MyThingy($hlist)"\n case _ => c.abort(c.enclosingPosition, "BOOM!")\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n最大的问题就在于减少\xe2\x80\x94,你需要从 开始HNil,而不是建立一个无意义的中间东西,然后附加它。您还需要捕获块的表达式,并将其键入为而Any不是Unit避免值丢弃。
(顺便说一句,我有点惊讶它可以作为白盒宏使用,但从 2.11.2 开始它就可以了。)
\n\n不过,我个人更喜欢这种带逗号的语法,而且也很简单:
\n\ndef describeImpl[L <: HList: c.WeakTypeTag](c: Context)(elems: c.Tree*) = {\n import c.universe._\n\n val hlist = elems.foldRight[c.Tree](q"shapeless.HNil: shapeless.HNil")(\n (h, t) => q"shapeless.::($h, $t)"\n )\n\n q"new MyThingy($hlist)"\n}\n\ndef describe[L <: HList](elems: Any*): MyThingy[L] = macro describeImpl[L]\nRun Code Online (Sandbox Code Playgroud)\n\n这里的用法与产品解决方案相同,但不涉及自动匹配。
\n