【问题标题】:How to build a dynamic sequence in a scala macro?如何在 scala 宏中构建动态序列?
【发布时间】:2013-10-31 17:50:27
【问题描述】:

我有一个输出嵌套案例类的 scala 宏。我可以组装使用 reify 创建的表达式片段,以编程方式构建嵌套案例类:

case class Foo(name: String)
case class Bar(foo: Foo)

def foo(name: String) = { 
  c.universe reify { 
    Foo(c.literal(name).splice)
  }
}

def bar(foo: Expr[Foo]) = {
  c.universe reify { 
    Bar(foo.splice)
  }
}

// output Bar(Foo("MyFoo"))
c.Expr( bar(foo("MyFoo").asInstanceOf[Expr[Foo]]).tree )

除了烦人的演员表之外,一切都很好(我该如何解决这个问题?)。我被卡住的地方是当我希望宏输出一些需要内部对象集合的案例类时,这些内部对象的大小根据宏在它正在处理的 AST 中看到的内容而变化。

所以,如果我有一个接受 foo 序列的类:

 case class Baz(foos: Seq[Foo])

我想做一些类似的事情:

def baz(foos: Seq[Expr[Foo]]): Expr[Baz] = {
  val flipped: Expr[Seq[Foo]] = ???  // convert foos from Seq[Expr[Foo]] to Expr[Seq[Foo]]
  c.universe reify {
    Baz(flipped.splice)
  }
}

// now pack any number of Foos into an output Baz as required
c.Expr(baz(Seq(foo("MyFoo1"),foo("MyFoo2"))).tree)

我无法将 Seq[Expr[Foo]] 转换为 Expr[Seq[Foo]] 来进行拼接,这样我就可以将可变数量的嵌套对象打包到宏输出中。如何具体化一个动态构建的列表以用作构造函数 arg?

【问题讨论】:

    标签: scala scala-2.10 scala-macros


    【解决方案1】:

    您可以向reify 提供类型参数以避免强制转换。将表达式序列由内而外翻转有点棘手,但并不多:

    case class Foo(name: String)
    case class Bar(foo: Foo)
    case class Baz(foos: Seq[Foo])
    
    import scala.language.experimental.macros
    import scala.reflect.macros.Context
    
    def demo: Baz = macro demo_impl
    def demo_impl(c: Context) = {
      import c.universe._
    
      def foo(name: String) = reify[Foo](Foo(c.literal(name).splice))
      def bar(foo: Expr[Foo]) = reify[Bar](Bar(foo.splice))
    
      def baz(foos: Seq[Expr[Foo]]): Expr[Baz] = {
        val flipped: Expr[Seq[Foo]] = c.Expr[Seq[Foo]](
          Apply(
            Select(reify(Seq).tree, newTermName("apply")),
            foos.map(_.tree).toList
          )
        )
    
        reify(Baz(flipped.splice))
      }
    
      baz(Seq(foo("MyFoo1"), foo("MyFoo2")))
    }
    

    生活是so much better in paradise,不过:

    def demo: Baz = macro demo_impl
    def demo_impl(c: Context) = {
      import c.universe._
    
      def foo(name: String) = c.Expr(q"Foo($name)")
      def bar(foo: Expr[Foo]) = c.Expr(q"Bar($foo)")
      def baz(foos: Seq[Expr[Foo]]) = c.Expr(q"Baz(Seq(..$foos))")
    
      baz(Seq(foo("MyFoo1"), foo("MyFoo2")))
    }
    

    这与第一个实现完全相同,但使用 quasiquotes(现在在 2.10 中作为编译器插件提供)而不是具体化和手动树构造。

    【讨论】:

    • 一个很好的答案。使用 reify 辅助函数可以很好地组合。它们对应于使用带有小写字母的案例类伴随对象,因此认知开销为零。无需让天堂编译器插件在大型代码库的 ci 构建上工作,就可以快速编写六个这样的帮助程序。 quasiquotes 语法也需要学习,所以我认为对于使用模式匹配将输入 AST 映射到包含六个嵌套案例类的丰富结构的这个简单用例来说,这可能有点过头了。再次感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-29
    • 2018-08-15
    • 2021-03-31
    • 1970-01-01
    • 1970-01-01
    • 2017-09-03
    • 1970-01-01
    相关资源
    最近更新 更多