在Scala

时间:2015-08-02 23:10:42

标签: scala annotations scala-macros

如果您运行此简单代码,您将看到以下内容:

object A {
  println("from A")
  var x = 0
}

object B {
  println("from B")
  A.x = 1
}

object Test extends App {
  println(A.x)
}

// Result:
// from A
// 0

你可以猜到,scala懒洋洋地初始化对象。对象B未初始化,它的工作方式与预期不符。我的问题是:我可以使用技巧来初始化对象B而无需访问它?我可以使用的第一个技巧是扩展具有一些特征的对象,并使用反射来初始化扩展特定特征的对象。我认为更优雅的方法是使用宏注释来注释对象:

@init
object B {
  println("from B")
  A.x = 1
}

class init extends StaticAnnotation {
  def macroTransform(annottees: Any*) = macro init.impl
}

object init {
  def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    import c.universe._

    // what should i do here ?
  }
}

但我有点困惑。如何在宏impl方法中从带注释的对象调用方法(以便初始化)?

1 个答案:

答案 0 :(得分:2)

我找到了解决方案:

<强>用法:

object A {
  println("from A")
  var x = 0
}

@init
object B {
  println("from B")
  A.x = 1
}

@init
object C {
  println("from C")
  A.x = 2
}

object Test extends App {
  init()
  println(A.x)
}

<强>输出:

from B
from A
from C
2

宏实施:

class init extends StaticAnnotation {
  def macroTransform(annottees: Any*) = macro init.impl
}

object init {
  private val objFullNames = new mutable.MutableList[String]()

  def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    import c.universe._

    annottees.map(_.tree).toList match {
      case (obj: ModuleDef) :: Nil =>
        objFullNames += c.typecheck(obj).symbol.fullName
      case _ =>
        c.error(c.enclosingPosition, "@init annotation supports only objects")
    }

    annottees.head
  }

  def apply() = macro runImpl

  def runImpl(c: whitebox.Context)(): c.Expr[Any] = {
    import c.universe._
    val expr = objFullNames.map(name => q"${c.parse(name)}.##").toList
    val res = q"{..$expr}"
    c.Expr(res)
  }
}