9

I was playing with Scala 2.11's new macro features. I wanted to see if I could do the following rewrite:

forRange(0 to 10) { i => println(i) }

// into

val iter = (0 to 10).iterator
while (iter.hasNext) {
  val i = iter.next
  println(i)
}

I think I got fairly close with this macro:

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)
}

This produces the following output when called as forRange(0 to 10) { i => println(i) } (at least, it's what the show function gives me on the resultant tree):

{
  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
      ()
  }
}

That looks like it should work, but there's a conflict between my manually defined val i and the i referenced in the spliced-in function body. I get the following error:

ReplGlobal.abort: symbol value i does not exist in$line38.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw. error: symbol value i does not exist in scala.reflect.internal.FatalError: symbol value i does not exist in $line38.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw.

And then a rather large stack trace, resulting in an "Abandoned crashed session" notification.

I can't tell if this is a problem with my logic (you simply can't splice in a function body that references a closed-over variable), or if it's a bug with the new implementation. The error reporting certainly could be better. It may be exacerbated by the fact that I'm running this on the Repl.

Is it possible to pull apart a function, separating the body from the closed-over terms, and rewrite it in order to splice the logic directly into a resulting tree?

4

2 回答 2

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来正确获取所有符号。

于 2013-12-18T20:07:48.900 回答
6

Oscar Boykin在 Twitter 上指出,我之前的答案不再有效,而且它也不是一个非常完整的答案——它解决了 Scala 2.10 上的 OP 指出的问题,但它不注意卫生——如果你写了iter => println(iter)你的话d 例如,编译时失败。

2.11 的更好实现是Transformer在取消类型检查后使用 a 重写树:

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

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

于 2015-12-05T03:05:29.530 回答