Scala宏如何引用'this'对象

3
我希望您能够用宏来消除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
    }
    }"""
}
1个回答

4
Contextprefix 方法提供对调用宏方法的表达式的访问,这应该可以帮助您实现您想要的功能。下面是一个快速示例,演示如何使用它:
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 提供给您的是一个表达式(expression),可能不是一个变量名:

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的这种方法比仅在宏中给局部变量加上一堆下划线要好得多,因为如果您包含一个恰好包含相同名称变量的表达式,这可能会导致问题。
(关于最后一段的更新:实际上我不确定是否会出现局部变量名称遮蔽可能在包含的树中使用的名称而导致问题。我自己避免这种情况,但目前我无法制造出它引起问题的示例,所以可能没问题。)

这回答了我的问题。 - Mike Beckerle
关于宏卫生和生成本地名称以避免交互的问题,我认为由于我的名称是有作用域的,所以不可能有任何交互。但是,那些在没有引入名称作用域的情况下扩展为代码的宏必须更加小心。 - Mike Beckerle
不用在意之前的评论。如果宏的主体恰好使用了与其名称相同的东西,而没有为它们引入新的作用域,那么它们将无意中捕获到新的定义。我应该像你建议的那样使用新的名称。 - Mike Beckerle

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