Macros 这种自由项变量错误(在宏展开时产生)可以避免吗?

Macros 这种自由项变量错误(在宏展开时产生)可以避免吗?,macros,scala-2.10,Macros,Scala 2.10,我正在开发一个DSL,在扩展宏时遇到了“自由项”故障。我想知道这是否可以避免。我已将问题简化为以下情况 假设我们有这个表达式: val list = join { 0 1 2 3 } println(list) 其中join是一个宏,其实现为: def join(c: Ctx)(a: c.Expr[Int]): c.Expr[List[Int]] = { import c.mirror._ a.tree match { case Block(list, ret)

我正在开发一个DSL,在扩展宏时遇到了“自由项”故障。我想知道这是否可以避免。我已将问题简化为以下情况

假设我们有这个表达式:

val list = join {
  0
  1
  2
  3
}
println(list)
其中join是一个宏,其实现为:

def join(c: Ctx)(a: c.Expr[Int]): c.Expr[List[Int]] = {
  import c.mirror._
  a.tree match {
    case Block(list, ret) =>
      // c.reify(List(new c.Expr(list(0)).eval, 
      //              new c.Expr(list(1)).eval,
      //              new c.Expr(list(2)).eval) :+ new c.Expr(ret).eval)
      c.reify((for (expr <- list) yield new c.Expr(expr).eval) :+ new c.Expr(ret).eval)
  }
}
def join(c:Ctx)(a:c.Expr[Int]):c.Expr[List[Int]={
导入c.mirror_
a、 树匹配{
案例块(列表,ret)=>
//具体化(列表)(新的c.Expr(列表(0)).eval,
//新的c.Expr(列表(1))评估,
//新c.Expr(列表(2)).eval:+新c.Expr(ret.eval)

c、 具体化((对于(expr)问题在于您的代码混合了编译时和运行时概念

您使用的“list”变量是一个编译时值(即,它应该在编译时迭代),您要求reify将其保留到运行时(通过拼接派生值)。这个跨阶段难题导致了所谓的自由项的创建

简言之,自由项是引用早期阶段值的存根。例如,以下代码段:

val x = 2
reify(x)
将汇编如下:

val free$x1 = newFreeTerm("x", staticClass("scala.Int").asTypeConstructor, x);
Ident(free$x1)
聪明吧?结果保留了x是一个Ident的事实,保留了它的类型(编译时特征),但是,尽管如此,也引用了它的值(运行时特征)。这是通过词法作用域实现的

但是,如果您试图从宏扩展(即内联到宏的调用站点)返回此树,则会发生问题。宏的调用站点很可能在其词法范围内没有x,因此它将无法引用x的值

更糟糕的是,如果上面的代码段是在宏中编写的,那么x只在编译时存在,即在运行编译器的JVM中。但是当编译器终止时,它就消失了

但是,包含对x的引用的宏扩展的结果应该在运行时运行(很可能在不同的JVM中运行)。要理解这一点,您需要跨阶段持久性,即以某种方式序列化任意编译时值并在运行时反序列化它们的能力。我不知道如何在Scala这样的编译语言中实现这一点


请注意,在某些情况下,跨阶段持久化是可能的。例如,如果x是静态对象的字段:

object Foo { val x = 2 }
import Foo._
reify(x)
这样,它就不会成为一个自由术语,而是以一种直接的方式具体化:

Select(Ident(staticModule("Foo")), newTermName("x"))
这是一个有趣的概念,SPJ在2012年Scala Days的演讲中也讨论了这个概念

为了验证某些表达式不包含自由项,在Haskell中,他们向编译器添加了一个新的内置原语,即
Static
type构造函数。对于宏,我们可以通过使用reify(它本身只是一个宏)自然地做到这一点。请参阅此处的讨论:


好的,现在我们已经看到了原始代码的问题所在,那么我们如何让它工作呢

不幸的是,我们不得不退回到手动AST构建,因为reify很难表达动态树。在宏观学中,reify的理想用例是拥有一个静态模板,其中包含在宏编译时已知的洞类型。请靠边一步,您将不得不求助于手动构建树

归根结底,您必须遵循以下几点(适用于最近发布的2.10.0-M4,请参阅scala语言的迁移指南,以了解到底发生了什么变化:):


scala 2.11中会有更简单的东西吗?准引号会有帮助吗?(我没有深入研究过宏,但每次尝试都会碰到这个问题)
import scala.reflect.makro.Context

object Macros {
  def join_impl(c: Context)(a: c.Expr[Int]): c.Expr[List[Int]] = {
    import c.universe._
    import definitions._
    a.tree match {
      case Block(list, ret) =>
        c.Expr((list :+ ret).foldRight(Ident(NilModule): Tree)((el, acc) => 
          Apply(Select(acc, newTermName("$colon$colon")), List(el))))
    }
  }

  def join(a: Int): List[Int] = macro join_impl
}