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

时间:2014-08-24 17:24:35

标签: scala macros type-inference shapeless

我有一个方法,它使用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&lt ;:formless.HList]

显然,编译器试图在宏扩展之前从块中推断出[Int, Boolean]。我也不明白为什么它需要两个参数,describeMyThing只需要一个参数。

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

2 个答案:

答案 0 :(得分:7)

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

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

答案 1 :(得分:4)

我在这里恭敬地不同意迈尔斯。我个人不能自动化,如果你想在你的项目中使用-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]

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