【问题标题】:Macro access to members of object where macro is defined对定义宏的对象成员的宏访问
【发布时间】:2013-06-22 18:30:26
【问题描述】:

假设我有一个特征 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.prefixc.eval(...) 等进行正常编译反射,而是找到了一个 2-project 解决方案:

项目 B:

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

项目A(取决于项目B):

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 并使用普通实例化或makeFoo 之类的宏来设置i 值。第二种方法允许我们在第一次编译时在编译时自定义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,而无需这种双重编译技巧吗?

【问题讨论】:

    标签: scala scala-macros


    【解决方案1】:

    事实证明,在宏内部访问Foo 的成员很容易,而无需求助于双重编译。以下是我们的示例宏如何访问i 的值:

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

    准确地说,i 这里实际上是一个 Expr,我们可以在 reify 作为一个数值内拼接和使用它。

    所以是的,可以在宏 (process) 内访问在单个编译中定义宏 (Foo) 的对象 (i) 的成员 (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
    }
    

    我将这个解决方案归功于 Francesco Bellomi/Eugene Burmako 在我找到的 Scala 用户列表中的一个问题/回答 here

    作为旁注,我们不一定需要使用 c.eval(...) 来从 Expr 中获取 实际值 一些无类型的 [正确这样说吗?] @987654337 @。在大多数情况下,我们应该很好地将值 包装在 Expr 中,因为我们可以通过将其拼接到 reify 中来使用该值 as 并使用那里的(拼接)值!

    【讨论】:

    • 另一个带有前缀stackoverflow.com/a/17028600/1296806 的示例,请参阅此答案的结尾以进行自我回答:stackoverflow.com/a/12486993/1296806
    • 我实际上也研究了您提到的其他答案。但是我没有从那个例子中的树解构-构造到使用前缀作为树构造的基础。好吧,我尝试了几种组合,但不是Select(c.prefix.tree, TermName("i")) 一种。
    猜你喜欢
    • 1970-01-01
    • 2013-06-23
    • 2022-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多