问题是您的代码混合了编译时和运行时概念。
您使用的“列表”变量是编译时值(即它应该在编译时迭代),并且您要求 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 日的演讲中也讨论了这个概念:http: //skillsmatter.com/podcast/scala/haskell-cloud。
为了验证某些表达式不包含自由项,在 Haskell 中,他们向编译器添加了一个新的内置原语,即Static
类型构造函数。使用宏,我们可以通过使用 reify(它本身只是一个宏)自然地做到这一点。请参阅此处的讨论:https ://groups.google.com/forum/#!topic/scala-internals/-42PWNkQJNA 。
好的,现在我们已经看到原始代码到底有什么问题,那么我们如何让它工作呢?
不幸的是,我们将不得不退回到手动 AST 构造,因为 reify 很难表达动态树。在宏观学中 reify 的理想用例是拥有一个静态模板,其中包含在宏编译时已知的漏洞类型。退一步——你将不得不依靠手工建造树木。
底线是您必须使用以下内容(适用于最近发布的 2.10.0-M4,请参阅 scala-language 的迁移指南以了解究竟发生了什么变化:http ://groups.google.com/group/scala -language/browse_thread/thread/bf079865ad42249c):
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
}