我正在开发DSL,并且在扩展宏时我遇到了"免费术语"失败.我想知道是否可以避免.我已将问题简化为以下情况.
假设我们有这个表达式:
val list = join {
0
1
2
3
}
println(list)
Run Code Online (Sandbox Code Playgroud)
其中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)
}
}
Run Code Online (Sandbox Code Playgroud)
宏的目的是连接参数块中的所有元素并将它们返回到单个列表中.由于块的内容可以是变量,我不能使用注释的reify(这很好用).未注释的 - 用于理解,生成免费术语 - 抛出消息:
"宏扩展包含由Macros.scala中的连接定义的自由项变量列表:48:18.在将此变量拼接成reifee时,您是否忘记使用eval?如果您有跟踪自由项变量的麻烦,请考虑使用-Xlog-free-术语"
有没有办法在不出现此错误的情况下引入for-comprehension(或迭代器或其他)?顺便说一下,我使用的是2.10-M3.
Eug*_*ako 16
问题是您的代码混合了编译时和运行时概念.
您正在使用的"list"变量是一个编译时值(即它应该在编译时被迭代),并且您要求Reify将其保留到运行时(通过拼接派生值).这个跨阶段的难题导致了所谓的免费术语的产生.
简而言之,自由术语是指从早期阶段的值引用的存根.例如,以下代码段:
val x = 2
reify(x)
Run Code Online (Sandbox Code Playgroud)
编译如下:
val free$x1 = newFreeTerm("x", staticClass("scala.Int").asTypeConstructor, x);
Ident(free$x1)
Run Code Online (Sandbox Code Playgroud)
聪明,是吗?结果保留了x是一个Ident的事实,它保留了它的类型(编译时特性),但是,它也引用了它的值(运行时特性).这可以通过词法范围来实现.
但是如果你试图从宏扩展(内联到宏的调用站点)返回这个树,事情就会爆发.宏的调用站点很可能在其词法范围内没有x,因此它无法引用x的值.
更糟糕的是什么.如果上面的代码片段是在宏内部编写的,则x仅在编译时存在,即在运行编译器的JVM中.但是当编译器终止时,它就消失了.
但是,包含对x的引用的宏扩展的结果应该在运行时运行(很可能在不同的JVM中).要理解这一点,您需要跨阶段持久性,即以某种方式序列化任意编译时值并在运行时反序列化它们的能力.我不知道如何用像Scala这样的编译语言来做这件事.
请注意,在某些情况下,跨阶段持久性是可能的.例如,如果x是静态对象的字段:
object Foo { val x = 2 }
import Foo._
reify(x)
Run Code Online (Sandbox Code Playgroud)
然后它不会以一个自由的术语结束,但会以一种简单的方式具体化:
Select(Ident(staticModule("Foo")), newTermName("x"))
Run Code Online (Sandbox Code Playgroud)
这是一个有趣的概念,也在SPJ在Scala Days 2012的演讲中进行了讨论: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语言的迁移指南,了解确切的更改内容: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
}
Run Code Online (Sandbox Code Playgroud)