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

9

我在尝试使用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: 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.
然后是一堆很长的堆栈跟踪信息,导致出现了“Abandoned crashed session”通知。
我无法确定这是我的逻辑问题(你可能无法插入引用闭合变量的函数体),还是新实现中的错误。错误报告肯定可以更好些。由于我在Repl上运行这个程序,这种情况可能会更加严重。
是否有可能拆分函数,将函数体与闭合项分开,并重新编写以便将逻辑直接嵌入到结果树中?

@KChalous,现在Scala 2.11已经删除了resetAllAttrs,你是否成功解决了这个问题?我遇到了完全相同的问题,我非常渴望解决它! - Andrew Bate
根据在线文档,仍然存在一个名为resetLocalAttrs的函数,它应该可以解决大多数情况。不知道这是否适用于此情况,但值得一试。参考:http://docs.scala-lang.org/overviews/macros/changelog211.html - KChaloux
根据Github上的scalamacros项目,@AndrewBate,resetLocalAttrs已被重命名为untypecheck。参考:https://github.com/scalamacros/resetallattrs - KChaloux
@KChalous 我进行了更深入的调查,在这个确切的例子中,我认为 untypecheck 就足够了。然而,当我尝试匹配模式 q"for ($i <- $collection) $body" 时,我需要旧的 resetAllAttrs(或者看起来是这样)。我已经在 Scala 2.11 宏中使用了 resetAllAttrs 库,并且它可以工作,而 untypecheck 单独则不行,但是我很快就遇到了 resetAllAttrs 破坏父树的常见问题。 - Andrew Bate
@AndrewBate 很遗憾,我对此并不是专家。通过那个 Github 链接,你可以找回 resetAllAttrs,但他们之所以删除它,正是因为你提到的破坏树的问题。不过,如果你需要它,请查看 http://github.com/scalamacros/resetallattrs。 - KChaloux
谢谢,我已经尝试过了,除了一些简单的例子外,它确实会破坏我的树结构。我会继续寻找解决方案... - Andrew Bate
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来确保所有符号正确。


我甚至不知道这个存在!非常好。 - KChaloux
3
很遗憾,它在2.11版本中被移除了。我也遇到了同样的问题,无法通过resetLocalAttrs(已弃用)或untypecheck来解决。 - Tomer Gabel

6
Oscar Boykin在Twitter上指出了我的先前回答已经不再适用,而且回答也不是很完整——它解决了Scala 2.10中由OP指出的问题,但对卫生问题并不仔细。例如,如果您编写iter => println(iter),您将会得到一个编译时失败。
更好的实现方法是在去类型检查后使用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

现在我们在函数文字中使用什么变量名都无所谓,因为它们将被替换为新的变量。

扩展宏技术的文档/手册在哪里可以找到,超越了简单的文本生成?例如 untypecheckcontext.info 和其他方法,这些方法使用代码作为 Scala 对象与类型类等而不是文本片段。我已经找到了http://docs.scala-lang.org/overviews/macros/usecases.html,但它只包含与文本替换相关的技术。 - ayvango
@ayvango 我对此没有一个好的答案。我有一堆演示项目、博客文章和SO答案,还有很多其他人也有,但它们没有以任何方式进行索引。 - Travis Brown

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接