使用宏构建列表时推断HList类型

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]在宏扩展之前从块中推断出来.我也不明白为什么它需要两个参数,其中describeMyThing只需要一个.

有没有办法让由宏生成的树驱动类型推断?

Mil*_*bin 7

如果您可以使用逗号分隔的参数列表,那么您可以遵循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)

一般来说,如果存在可行的非宏替代方案,我的建议是避免使用宏.


Tra*_*own 4

我在此谨表示不同意迈尔斯的观点。我个人无法忍受自动调整,如果你想-Xlint在你的项目中使用,他的答案中的解决方案会引起很多警告噪音。我绝对同意,当有可行的替代方案时,您应该避免使用宏,但是如果我必须在自动调整和宏之间进行选择,而我只是提供语法糖,那么我会选择宏。

\n\n

在你的情况下,这并不太难\xe2\x80\x94,你的逻辑中只有一个小错误(嗯,两个,真的)。以下内容将正常工作:

\n\n
import 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]\n
Run Code Online (Sandbox Code Playgroud)\n\n

或者更简洁地说:

\n\n
def 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}\n
Run Code Online (Sandbox Code Playgroud)\n\n

最大的问题就在于减少\xe2\x80\x94,你需要从 开始HNil,而不是建立一个无意义的中间东西,然后附加它。您还需要捕获块的表达式,并将其键入为而Any不是Unit避免值丢弃。

\n\n

(顺便说一句,我有点惊讶它可以作为白盒宏使用,但从 2.11.2 开始它就可以了。)

\n\n

不过,我个人更喜欢这种带逗号的语法,而且也很简单:

\n\n
def 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]\n
Run Code Online (Sandbox Code Playgroud)\n\n

这里的用法与产品解决方案相同,但不涉及自动匹配。

\n