如何确定传递给宏的表达式是否总是会产生相同的值?

时间:2015-07-27 13:16:15

标签: scala scala-macros

我们假设我已经定义了一个宏,如下所示。它本质上是一个类型为T的表达式,并返回一个MyType [T]类型的对象(涉及的实际类型并不重要)。

x = regexp(Newton ,'*','split')
y = regexp(Newton ,'/','split')
z = regexp(Newton ,'^','split')

在我的宏中,我想确定传递的表达式是否是常量。我的意思是,我想知道一旦在运行时进行评估,表达式是否可以随后评估为不同的值。如果它是常量,我可以应用某些非常有用的优化。

我知道表达式是常数,如果它是:

  • 文字表达。
  • a' this'表达
  • 对val或参数的引用。
  • 成员调用,其中对象表达式是常量,被调用的成员是val或lazy val。

例如,下面调用callMacro的前五次调用中传递的表达式应该被视为常量:

object MyMacro {
  def macroImpl[T : context.WeakTypeTag, U : context.WeakTypeTag](context : scala.reflect.macros.blackbox.Context) (expression : context.Expr[T]) : context.Expr[U] =
}

object MyObj {
  def callMacro[T](expression : T) : MyType[T] = macro MyMacro.macroImpl[T, MyType[T]]
}

我已经为前两种情况实施了代码(这相当简单)。

class MyClass {
  val i = 0

  val myLiteral = callMacro("Hi!") //constant - literal expression

  val myThis = callMacro(this) //constant - this expression

  val myInt = callMacro(i) //constant - reference to a val

  def myMethod(p : MyOtherClass) {
    val myParameter = callMacro(p) //constant - reference to a parameter
    val myValMember = callMacro(p.x) //constant - invocation of val member
    val myVarMember = vallMacro(p.y) //NOT constant - invocation of var member
    val myVarMember = vallMacro(p.z) //NOT constant - invocation of def member
  }
}

class MyOtherClass(val x : Int, var y : Int) {
  def z = x + y
}

但是,我不确定这样的事情是否已经存在,或者是否可能检测到对象上被调用的成员是否为val。

是否可以实施第四项标准?或者,API中是否存在类似的内容?

1 个答案:

答案 0 :(得分:1)

我找到了解决方案。它基本上归结为我不知道规模的反射系统中的符号。

我最后添加了第五个标准来处理引用隐式参数或对象的情况。

implicit class extendSymbol(symbol : scala.reflect.macros.blackbox.Context#Symbol) {
  def isStable =
    (symbol.isTerm && symbol.asTerm.isStable) || (symbol.isMethod && symbol.asMethod.isStable)
}

def isConstant[T](context : scala.reflect.macros.blackbox.Context) (tree : context.Tree) : Boolean = {
  import context.universe._
  tree match {
    case This(_) =>
      true
    case Literal(_) =>
      true
    case ident @ Ident(_) =>
      ident.symbol.isStable
    case select @ Select(objExpr, term) =>
      isConstant(context) (objExpr) && select.symbol.isStable
    //for implicit values
    case Apply(TypeApply(Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("implicitly")), _), _) =>
      true
    case _ =>
      false
  }
}