我在玩Scala 2.11的新宏功能。我想看看我是否可以重写以下内容:
forRange(0 to 10) { i => println(i) }
// into
val iter = (0 to 10).iterator
while (iter.hasNext) {
val i = iter.next
println(i)
}
我想我已经非常接近这个宏了:
def _forRange[A](c: BlackboxContext)(range: c.Expr[Range])(func: c.Expr[Int => A]): c.Expr[Unit] = {
import c.universe._
val tree = func.tree match {
case q"($i: $t) => $body" => q"""
val iter = ${range}.iterator
while (iter.hasNext) {
val $i = iter.next
$body
}
"""
case _ => q""
}
c.Expr(tree)
}
当被调用为forRange(0 to 10) { i => println(i) }
时,它会产生以下输出(至少,这是show
函数在结果树上给我的):
{
val iter = scala.this.Predef.intWrapper(0).to(10).iterator;
while$1(){
if (iter.hasNext)
{
{
val i = iter.next;
scala.this.Predef.println(i)
};
while$1()
}
else
()
}
}
该看起来应该工作,但我的手动定义的val i
和拼接函数体中引用的i
之间存在冲突。我得到以下错误:
ReplGlobal.abort:符号值i不存在于$line38.$read$$iw$$iw$$iw$$iw$$iw$$中。错误:符号值i不存在于scala.reflect.internal.FatalError:符号值i不存在于$line38.$read$$iw$$iw$$iw$$iw$$iw$$中。
然后是一个相当大的堆栈跟踪,导致"放弃崩溃的会话"通知。
我不知道这是我的逻辑有问题(你根本无法拼接引用封闭变量的函数体),还是新实现的错误。错误报告当然可以更好。我在Repl上运行这一事实可能会加剧这种情况。
是否可以拆开一个函数,将主体与封闭项分离,并重写它,以便将逻辑直接拼接到结果树中?
有疑问时,resetAllAttrs
:
import scala.language.experimental.macros
import scala.reflect.macros.BlackboxContext
def _forRange[A](c: BlackboxContext)(range: c.Expr[Range])(
func: c.Expr[Int => A]
): c.Expr[Unit] = {
import c.universe._
val tree = func.tree match {
case q"($i: $t) => $body" => q"""
val iter = ${range}.iterator
while (iter.hasNext) {
val $i = iter.next
${c.resetAllAttrs(body)} // The only line I've changed.
}
"""
case _ => q""
}
c.Expr(tree)
}
然后:
scala> def forRange[A](range: Range)(func: Int => A) = macro _forRange[A]
defined term macro forRange: [A](range: Range)(func: Int => A)Unit
scala> forRange(0 to 10) { i => println(i) }
0
1
2
3
4
5
6
7
8
9
10
一般来说,当你从一个地方抓一棵树并把它扔到另一个地方时,可能需要使用resetAllAttrs
来获得所有正确的符号。
Oscar Boykin在推特上指出,我之前的答案已经不起作用了,而且无论如何都不是一个非常完整的答案——它解决了Scala 2.10上OP指出的问题,但它不注意卫生——例如,如果你写iter => println(iter)
,你会遇到编译时失败的问题。
2.11的一个更好的实现是在取消类型检查后使用Transformer
重写树:
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
def _forRange[A](c: Context)(r: c.Expr[Range])(f: c.Expr[Int => A]): c.Tree = {
import c.universe._
f.tree match {
case q"($i: $_) => $body" =>
val newName = TermName(c.freshName())
val transformer = new Transformer {
override def transform(tree: Tree): Tree = tree match {
case Ident(`i`) => Ident(newName)
case other => super.transform(other)
}
}
q"""
val iter = ${r.tree}.iterator
while (iter.hasNext) {
val $newName = iter.next
${ transformer.transform(c.untypecheck(body)) }
}
"""
}
}
def forRange[A](r: Range)(f: Int => A): Unit = macro _forRange[A]
它是这样工作的:
scala> forRange(0 to 10)((i: Int) => println(i))
0
1
2
3
4
5
6
7
8
9
10
现在,我们在函数文字中使用什么变量名并不重要,因为它无论如何都会被一个新的变量替换。