【问题标题】:Splicing a passed function body into a macro-rewritten expression将传递的函数体拼接成宏重写的表达式
【发布时间】:2013-12-18 18:33:33
【问题描述】:

我在玩 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 ii引用之间存在冲突拼接的功能体。我收到以下错误:

ReplGlobal.abort:$line38.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw 中不存在符号值 i。 错误:符号值 i 不存在 scala.reflect.internal.FatalError:$line38 中不存在符号值 i。$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw。

然后是相当大的堆栈跟踪,导致“Abandoned crashed session”通知。

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

是否可以拆分一个函数,将主体与封闭项分开,然后重写它以便将逻辑直接拼接到结果树中?

【问题讨论】:

  • @KChalous 既然 resetAllAttrs 已从 Scala 2.11 中删除,您是否曾经设法解决此问题?我有完全同样的问题,我迫切希望解决它!
  • @AndrewBate 根据在线文档,resetLocalAttrs 仍然存在,应该涵盖大多数情况。不知道这是不是其中之一,但值得一试。参考:docs.scala-lang.org/overviews/macros/changelog211.html
  • @AndrewBate 并根据 github 上的 scalamacros 项目,resetLocalAttrs 已重命名为 untypecheck。参考:github.com/scalamacros/resetallattrs
  • @KChalous 我已经调查了更多,在这个 exact 示例中,我认为untypecheck 就足够了。但是,当我尝试匹配模式q"for ($i <- $collection) $body" 时,我需要旧的resetAllAttrs(或者看起来如此)。我已经为 Scala 2.11 宏使用了 resetAllAttrs 库,它可以工作,而单独的 untypecheck 不能,但是我很快遇到了 resetAllAttrs 破坏父树的常见问题。
  • @AndrewBate 不幸的是,我绝对不是这方面的专家。看起来,通过该 github 链接,您可以返回 resetAllAttrs,但由于您提到的树损坏问题,他们专门删除了它。不过,如果您需要它,请查看github.com/scalamacros/resetallattrs

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


【解决方案1】:

如有疑问,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 来获取所有符号。

【讨论】:

  • 甚至不知道这存在!非常好。
  • 不幸的是它在 2.11 中被删除了。我遇到了同样的问题,但无法通过 resetLocalAttrs(已弃用)或 untypecheck 解决。
【解决方案2】:

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

现在我们在函数字面量中使用什么变量名已经无关紧要了,因为无论如何它都会被一个新的变量替换。

【讨论】:

  • 在哪里可以找到超越简单文本生成的扩展宏技术的文档/手册?像untypecheckcontext.info 和其他将代码用作具有类型类等的 scala 对象而不是文本 sn-ps 的方法。我找到了docs.scala-lang.org/overviews/macros/usecases.html,但它只包含与文本替换相关的技术。
  • @ayvango 我对此没有很好的答案。我有很多演示项目、博客文章和 SO 答案,很多其他人也有,但它们没有以任何方式编入索引。
猜你喜欢
  • 2019-10-18
  • 2021-12-09
  • 2010-11-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-21
  • 1970-01-01
相关资源
最近更新 更多