scala宏指的是'this'对象

时间:2016-01-25 16:53:30

标签: 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
    }
    }"""
}

1 个答案:

答案 0 :(得分:4)

prefix上的Context方法提供了对调用宏方法的表达式的访问权限,这可以让您完成您尝试执行的操作。以下是如何使用它的快速示例:

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)比仅使用一堆下划线为宏中的局部变量添加前缀要好得多,如果包含一个恰好包含变量的表达式,这可能会导致问题同名。

(关于最后一段的更新:实际上我不记得你是否可以确定你是否可以解决局部变量名称可能在包含的树中使用的名称。我自己避免使用它,但是我无法制造一个例子,它现在导致问题,所以它可能没问题。)