这是一个简化的例子,但问题仍然存在。
我想使用宏(基于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")))
这样的树(可以访问本地值的值)转换为一个名称一个字符串,该字符串可以用作{{{{ 1}?有可能吗?
更新
这里真正的问题是gist!
提前致谢,
答案 0 :(得分:3)
我很难理解你想要实现的目标,以及为什么你到处都在使用树。树木真的很低级,难以使用,很棘手,而且很难理解代码的作用。 Quasiquotes(http://docs.scala-lang.org/overviews/macros/quasiquotes.html)是确实可行的方法,你可以在scala 2.10.x生产版上使用它们,这要归功于宏天堂插件(http://docs.scala-lang.org/overviews/macros/paradise.html)。您可以简单地编写q"(a: Int) => {val z = "toShort"; a.z}"
并直接获取刚刚输入的树表达式。
要回答您的问题,第一点是要记住在编译时评估宏。因此,它们无法生成依赖于运行时值的代码。这就是编译器抱怨你splice
的原因。但是,如果传递的值可以在编译时计算,通常是文字,那么您可以使用eval在宏代码中获取其值。如scaladoc所示,Eval确实遇到了一个bug。它应该只在无类型树上调用。因此,在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
希望它有所帮助;))