макрос scala, который ссылается на этот объект

Я пытаюсь использовать макрос, чтобы устранить необходимость в scala для создания объекта функции, передаваемого вниз. Этот код используется во внутренних циклах нашей системы, и мы не хотим, чтобы внутренний цикл просто бесконечно выделял объекты. Это создает для нас проблемы с производительностью.

Наш исходный код был таким:

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

И тело было функцией, которая была передана как функциональный объект.

У меня проблема в том, что исходная версия без макросов относится к «этому». Мой обходной путь ниже заключается в том, чтобы каждое место, где вызывается макрос, передавали «этот» объект в качестве другого аргумента. то есть, уродливый, как:

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
    }
    }"""
}

person Mike Beckerle    schedule 25.01.2016    source источник


Ответы (1)


Метод 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) намного лучше, чем простое добавление в макрос перед локальными переменными нескольких символов подчеркивания, что может вызвать проблемы, если вы включаете выражение, содержащее переменную с таким же именем.

(Обновление относительно последнего абзаца: на самом деле я точно не помню, можете ли вы столкнуться с проблемами с именами локальных переменных, затеняющими имена, которые могут использоваться во включенных деревьях. Я сам избегаю этого, но я не могу создать пример это вызывает проблемы в данный момент, так что это может быть хорошо.)

person Travis Brown    schedule 25.01.2016
comment
Это ответило на мой вопрос. - person Mike Beckerle; 25.01.2016
comment
К вашему сведению о макрогигиене и создании локальных имен, чтобы избежать взаимодействий. Я думаю, поскольку мои имена ограничены, никакого взаимодействия быть не может. Но макросы, которые расширяются в код без введения области имени, должны быть гораздо более осторожными. - person Mike Beckerle; 25.01.2016
comment
Не обращайте внимания на предыдущий комментарий. Если тело макроса просто так использует вещи, которые используют те же имена, не вводя для них новую область, то они непреднамеренно захватили новые определения. Я должен использовать новые имена, как вы предложили. - person Mike Beckerle; 26.01.2016