引用 'this' 对象的 Scala 宏
scala macro that refers to 'this' object
我正在尝试使用宏来消除 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
)比仅在宏中为局部变量加上一堆下划线前缀要好得多,如果包含恰好包含变量的表达式,这可能会导致问题同名
(关于最后一段的更新:实际上我不确定你是否会遇到局部变量名隐藏可能在包含的树中使用的名称的问题。我自己避免了它,但我可以'目前不制造它导致问题的例子,所以它可能没问题。)
我正在尝试使用宏来消除 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
)比仅在宏中为局部变量加上一堆下划线前缀要好得多,如果包含恰好包含变量的表达式,这可能会导致问题同名
(关于最后一段的更新:实际上我不确定你是否会遇到局部变量名隐藏可能在包含的树中使用的名称的问题。我自己避免了它,但我可以'目前不制造它导致问题的例子,所以它可能没问题。)