将传递的函数体拼接成宏重写的表达式

时间:2013-12-18 18:33:33

标签: scala macros expression scala-macros scala-quasiquotes

我正在使用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:符号值我在$ line38中不存在。$ read $$ iw $$ iw $$ iw $$ iw $$ iw $$ iw $$ iw $$ iw。   错误:符号值我不存在   scala.reflect.internal.FatalError:符号值i在$ line38中不存在。$ read $$ iw $$ iw $$ iw $$ iw $$ iw $$ iw $$ iw $$ iw。

然后是一个相当大的堆栈跟踪,导致“被遗弃的崩溃会话”通知。

我无法分辨这是否是我的逻辑问题(你根本无法在引用封闭变量的函数体中拼接),或者它是否是新实现的错误。错误报告肯定会更好。我在Repl上运行它可能会加剧这种情况。

是否可以拆开一个函数,将正文与封闭的术语分开,并重写它以便将逻辑直接拼接到结果树中?

2 个答案:

答案 0 :(得分:7)

如有疑问,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来使所有符号正确。

答案 1 :(得分:5)

Oscar Boykin指出on Twitter我之前的回答已经不再适用了,反正它也不是一个非常完整的答案 - 它解决了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

现在我们在函数文字中使用的是什么变量名称并不重要,因为它无论如何都会被一个新变量替换。