2

这是一个简化的示例,但问题仍然存在。

我想使用宏(基于scala的伪代码)来实现这一点:

(a: Int) => {
  val z = "toShort"
  a.z
}

如果我具体化它,我会得到类似的东西:

Function(
  List(
    ValDef(
      Modifiers(Flag.PARAM),
      newTermName("a"),
      Ident(scala.Int),
      EmptyTree
    )
  ),
  Block(
    List(
      ValDef(
        Modifiers(),
        newTermName("z"),
        TypeTree(),
        Literal(Constant("toShort"))
      )
    ),
    Apply(
      Select(
        Ident(newTermName("a")),
        newTermName("toShort")
      ),
      List()
    )
  )
)

我不知道如何访问一个值,然后将其用作 TermName。

我尝试替换newTermName("toShort")为,newTermName(c.Expr[String](Select(Ident(newTermName("z")))).splice)但编译器似乎不喜欢:

宏展开时出现异常:java.lang.UnsupportedOperationException:你调用的函数没有被>编译器拼接。这意味着涉及跨阶段评估,需要显式调用。如果您确定这不是疏忽,请将 scala-compiler.jar 添加到类路径中,然后导入scala.tools.reflect.Eval并调用<your expr>.eval

我也按照编译器的建议尝试了“eval”:newTermName(c.eval(c.Expr[String](...))但都没有奏效。

我怎样才能将树Select(Ident(newTermName("z")))(这是对本地 val 的值的访问)之类的树转换为 Name一个可以用作参数的字符串newTermName?可能吗?

更新:

这里真正的问题给你带来了一个要点

提前致谢,

4

1 回答 1

3

我很难理解您要实现的目标,以及为什么到处都使用 Trees。树是非常低级的,很难使用,很棘手,而且很难理解代码的作用。Quasiquotes ( http://docs.scala-lang.org/overviews/macros/quasiquotes.html ) 确实是要走的路,由于宏天堂插件 ( http:// /docs.scala-lang.org/overviews/macros/paradise.html)。您可以简单地编写q"(a: Int) => {val z = "toShort"; a.z}"并直接获得您刚刚键入的树表达式。

要回答您的问题,第一点是要记住宏是在编译时评估的。因此,它们无法生成依赖于运行时值的代码。这就是编译器抱怨你的splice. 但是如果你传递一个可以在编译时计算的值,通常是一个文字,那么你可以使用 eval 在你的宏代码中获取它的值。正如 scaladoc 中所指出的那样,Eval 确实遇到了一个错误。它应该只在无类型树上调用。s: c.Expr[String]因此,在表达式上调用 eval 的方法是val s2 = c.eval(c.Expr[String](c.resetAllAttrs(c.tree.duplicate)))为您提供String然后您可以在代码中正常使用的方法,例如q"(a: Int) => a.${newTermName(s2)}".

总而言之,假设您创建了一个宏,该宏将从对象及其String字段之一输出字符串值。它会给出类似的东西

def attr[A](a: A, field: String): String = macro attrImpl[A]

def attrImpl[A: c.WeakTypeTag](c: Context)(a: c.Expr[A], field: c.Expr[String]) = {
  import c.universe._

  val s = c.eval(c.Expr[String](c.resetAllAttrs(field.tree.duplicate)))
  c.Expr[String](q"a.${newTermName(s)}")

}

REPL 会话测试:

scala> object a { val field1 = "field1"; val field2 = "field2" }
defined module a

scala> attr(a, "field1")
res0: String = field1

scala> attr(a, "field2")
res1: String = field2

要了解编译时间和运行时之间的区别,您可以在 REPL 中思考以下结果;-)

scala> val s = "field1"; attr(a, s)
error: exception during macro expansion: 
scala.tools.reflect.ToolBoxError: reflective compilation has failed: 

$iw is not an enclosing class
    at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.throwIfErrors(ToolBoxFactory.scala:311)
    at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.compile(ToolBoxFactory.scala:244)
    at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.compile(ToolBoxFactory.scala:408)
    at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.eval(ToolBoxFactory.scala:411)
    at scala.reflect.macros.runtime.Evals$class.eval(Evals.scala:16)
    at scala.reflect.macros.runtime.Context.eval(Context.scala:6)
    at .attrImpl(<console>:14)


scala> val s = "field1"
s: String = field1

scala> attr(a, s)
res3: String = field1

希望能帮助到你 ;))

于 2013-08-27T20:03:22.340 回答