Доступ макросов к членам объекта, где определен макрос

Скажем, у меня есть трейт Foo, который я инстанцирую с начальным значением i.

val foo = new Foo(6) // class Foo(i: Int)

и позже я звоню secondMethod, который, в свою очередь, звонит myMacro

foo.secondMethod(7) // def secondMethod(j: Int) = macro myMacro 

тогда как myMacro может найти начальное значение i (6)?

Мне не удалось нормальное отражение компиляции с использованием c.prefix, c.eval(...) и т. д., но вместо этого я нашел решение с двумя проектами:

Проект Б:

object CompilationB {
    def resultB(x: Int, y: Int) = macro resultB_impl
    def resultB_impl(c: Context)(x: c.Expr[Int], y: c.Expr[Int]) =
      c.universe.reify(x.splice * y.splice)
}

Проект А (зависит от проекта Б):

trait Foo {
  val i: Int

  // Pass through `i` to compilation B:
  def apply(y: Int) = CompilationB.resultB(i, y)
}

object CompilationA {
  def makeFoo(x: Int): Foo = macro makeFoo_impl
  def makeFoo_impl(c: Context)(x: c.Expr[Int]): c.Expr[Foo] =
    c.universe.reify(new Foo {val i = x.splice})
}

Мы можем создать Foo и установить значение i либо с помощью обычного создания экземпляра, либо с помощью макроса, такого как makeFoo. Второй подход позволяет нам настроить Foo во время компиляции в первой компиляции, а затем во второй компиляции дополнительно настроить его реакцию на ввод (в данном случае i)! Каким-то образом мы получаем «мета-мета»-возможности (или «патафизические»-возможности ;-)

Обычно нам нужно иметь foo в области видимости для самоанализа i (например, c.eval(...)). Но, сохранив значение i внутри объекта Foo, мы можем получить к нему доступ в любое время и создать экземпляр Foo где угодно:

object Test extends App {
  import CompilationA._

  // Normal instantiation
  val foo1 = new Foo {val i = 7}
  val r1   = foo1(6)

  // Macro instantiation
  val foo2 = makeFoo(7)
  val r2   = foo2(6)

  // "Curried" invocation
  val r3 = makeFoo(6)(7)

  println(s"Result 1 2 3: $r1 $r2 $r3")
  assert((r1, r2, r3) ==(42, 42, 42))
}

Мой вопрос

Могу ли я найти i внутри моего примера макросов без этой двойной хакерской компиляции?


person Marc Grue    schedule 22.06.2013    source источник


Ответы (1)


Оказалось, что легко получить доступ к членам Foo внутри макроса, не прибегая к двойной компиляции. Вот как наш пример макроса может получить доступ к значению i:

    val i = c.Expr[Int](Select(c.prefix.tree, TermName("i")))
    reify(i.splice * j.splice)

Чтобы быть точным, i здесь на самом деле является Expr, который мы можем соединить и использовать внутри reify как числовое значение.

Так что да, внутри макроса (process) можно получить доступ к членам (i) объекта (Foo), где макрос определен (Foo) в рамках одной компиляции (под «определено» я имею в виду, где я использую ключевое слово macro) :

object compilation {
  def makeFoo(x: Int): Foo = macro makeFoo_impl
  def makeFoo_impl(c: Context)(x: c.Expr[Int]): c.Expr[Foo] =
    c.universe.reify(new Foo {val i = x.splice})

  def process(c: Context)(j: c.Expr[Int]): c.Expr[Int] = {
    import c.universe._
    // Foo.i accessed inside macro
    val i = c.Expr[Int](Select(c.prefix.tree, TermName("i")))
    reify(i.splice * j.splice)
  }
}

trait Foo {
  val i: Int
  def apply(j: Int) = macro compilation.process
}

object Test extends App {
  import compilation._

  val foo1 = new Foo {val i = 6}
  Console println foo1(7) // 42

  val foo2 = makeFoo(6)
  Console println foo2(7) // 42

  Console println makeFoo(6)(7) // 42
}

Я обязан этим решением вопросу/ответу Франческо Белломи/Юджина Бурмако из списка пользователей Scala, который я нашел здесь.

В качестве примечания: нам не обязательно использовать c.eval(...) для получения фактического значения из Expr некоторого нетипизированного [правильно так говорить?] Tree. В большинстве случаев у нас должно получиться, что значение обернуто в Expr, так как мы можем использовать это как значение, объединив его внутри reify и выполнив все наши вычисления с помощью ( склейка-)значение есть!

person Marc Grue    schedule 24.06.2013
comment
Другой пример с префиксом stackoverflow.com/a/17028600/1296806 и см. конец этого ответа для самостоятельного ответа: < href="http://stackoverflow.com/a/12486993/1296806">stackoverflow.com/a/12486993/1296806 - person som-snytt; 25.06.2013
comment
Я действительно изучил другой ответ, на который вы ссылаетесь. Но я не сделал мысленного скачка от деконструкции-конструкции Дерева в этом примере к использованию префикса в качестве основы для построения Дерева. Ну, я пробовал несколько комбинаций, но не Select(c.prefix.tree, TermName("i")). - person Marc Grue; 25.06.2013