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

 手机用户2502861123 发布于 2023-02-09 13:29

我正在玩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:符号值i在$ 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.

然后是一个相当大的堆栈跟踪,导致"Abandoned崩溃会话"通知.

我无法判断这是否是我的逻辑问题(你根本无法在引用封闭变量的函数体中拼接),或者它是否是新实现的错误.错误报告肯定会更好.可能因为我在Repl上运行它而加剧了这一点.

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

2 个回答
  • 如有疑问,请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用来使所有的符号都正确.

    2023-02-09 13:31 回答
  • Oscar Boykin 在推特上指出我之前的回答已经不再有效了,反正它也不是一个非常完整的答案 - 它解决了SC在Scala 2.10上指出的问题,但它并不小心卫生 - 如果你写了iter => println(iter)你的话例如,d会出现编译时失败.

    2.11的更好实现将使用a 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
    

    现在我们在函数文字中使用的变量名称无关紧要,因为它只会被一个新变量替换.

    2023-02-09 13:34 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有