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



我有一个方法,使用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]。我也不明白为什么它需要两个参数,而describeMyThing只需要一个参数。

是否有一种方法可以由宏生成的树驱动类型推断?

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

此处的用法与产品解决方案相同,但不涉及自动双元化。

最新更新