首页 文章

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

提问于
浏览
6

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

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

2 回答

  • 7

    我支持自动翻译,如果你想在你的项目中使用 -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 开始,而不是 Build 一个毫无意义的中间事物,然后再加以解决 . 您还需要捕获块的表达式,并将其键入 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]
    

    此处的用法与产品解决方案相同,但不涉及自动翻译 .

  • 4

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

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

相关问题