引用 'this' 对象的 Scala 宏

标签 scala scala-macros

我正在尝试使用宏来消除 scala 构造向下传递的函数对象的需要。这段代码在我们系统的内部循环中使用,我们不希望内部循环无休止地分配对象。这给我们带来了性能问题。

我们原来的代码是这样的:

dis.withBitLengthLimit(newLimit){... body ...}

主体是作为函数对象传入的函数。

我遇到的问题是原始非宏版本指的是“this”。我下面的解决方法是让调用宏的每个地方都将“this”对象作为另一个参数传递。即,丑陋如:

dis.withBitLengthLimit(dis, newLimit){... body ...}

这并不糟糕,但确实似乎没有必要传递 dis

有没有更简洁的方法?

这是下面的宏。

object IOMacros {
  /**
   * Used to temporarily vary the bit length limit.
   *
   * Implementing as a macro eliminates the creation of a downward function object every time this
   * is called.
   *
   * ISSUE: this macro really wants to use a self reference to `this`. But when a macro is expanded
   * the object that `this` represents changes. Until a better way to do this comes about, we have to pass
   * the `this` object to the `self` argument, which makes calls look like:
   *     dis.withBitLengthLimit(dis, newLimit){... body ...}
   * That looks redundant, and it is, but it's more important to get the allocation of this downward function
   * object out of inner loops.
   */
  def withBitLengthLimitMacro(c: Context)(self: c.Tree, lengthLimitInBits: c.Tree)(body: c.Tree) = {

    import c.universe._

    q"""{
    import edu.illinois.ncsa.daffodil.util.MaybeULong

    val ___dStream = $self
    val ___newLengthLimit = $lengthLimitInBits
    val ___savedLengthLimit = ___dStream.bitLimit0b

    if (!___dStream.setBitLimit0b(MaybeULong(___dStream.bitPos0b + ___newLengthLimit))) false
    else {
      try {
        $body
      } finally {
        ___dStream.resetBitLimit0b(___savedLengthLimit)
      }
      true
    }
    }"""
}

最佳答案

Context 上的prefix 方法提供了对调用宏方法的表达式的访问,这应该允许您完成您想要做的事情。以下是如何使用它的简单示例:

import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context

class Foo(val i: Int) {
  def bar: String = macro FooMacros.barImpl
}

object FooMacros {
  def barImpl(c: Context): c.Tree = {
    import c.universe._

    val self = c.prefix

    q"_root_.scala.List.fill($self.i + $self.i)(${ self.tree.toString }).mkString"
  }
}

然后:

scala> val foo = new Foo(3)
foo: Foo = Foo@6fd7c13e

scala> foo.bar
res0: String = foofoofoofoofoofoo

请注意,您需要注意一些问题。 prefix 为您提供表达式,它可能不是变量名:

scala> new Foo(2).bar
res1: String = new Foo(2)new Foo(2)new Foo(2)new Foo(2)

这意味着如果表达式有副作用,您必须注意不要将它多次包含在结果树中(假设您不希望它们多次发生):

scala> new Qux(1).bar
hey
hey
res2: String = new Qux(1)new Qux(1)

这里构造函数被调用了两次,因为我们在宏的结果中包含了两次 prefix 表达式。您可以通过在宏中定义一个临时变量来避免这种情况:

object FooMacros {
  def barImpl(c: Context): c.Tree = {
    import c.universe._

    val tmp = TermName(c.freshName)
    val self = c.prefix

    q"""
    {
      val $tmp = $self

      _root_.scala.List.fill($tmp.i + $tmp.i)(${ self.tree.toString }).mkString
    }
    """
  }
}

然后:

scala> class Qux(i: Int) extends Foo(i) { println("hey") }
defined class Qux

scala> new Qux(1).bar
hey
res3: String = new Qux(1)new Qux(1)

请注意,这种方法(使用 freshName)比仅在宏中为局部变量加上一堆下划线前缀要好得多,如果您包含的表达式恰好包含同名变量。

(关于最后一段的更新:实际上我不确定你是否会遇到局部变量名隐藏可能在包含的树中使用的名称的问题。我自己避免了它,但我不能制造它目前导致问题的一个例子,所以它可能没问题。)

关于引用 'this' 对象的 Scala 宏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34997930/

相关文章:

scala - 从 Scala 宏中按名称访问类定义

Scala 案例类测试

java - 是否有 Clojure 或 Scala 的持久不可变 vector 的 Java 版本?

scala - 作为守护进程运行 SBT

scala - 如何使用宏注释将无参数构造函数添加到 Scala 案例类?

scala - 测试 c.universe.Type 是否可分配给宏中的另一种类型

scala - DSL 提取案例类字段名称

scala - 在scala宏中推断树的类型

scala - 将 Dataframe 中的 spark 模式与类型 T 进行比较

scala - 设置 sbt scalafx 项目