Automatically initialize an object in Scala
If you run this simple code, you will see the following:
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
As you can guess, scala initializes objects lazily. Object B is not initialized and does not work as expected. My question here is what tricks can I use to initialize object B without accessing it? The first trick I can use is to extend an object with some trait and use reflection to initialize the object that extends a specific trait. I think a more elegant way is to annotate an object with a macro:
@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 ?
}
}
But I was a little confused. How do I call methods (for initialization) from an annotated object in a macro method impl
?
+3
source to share
1 answer
I found a solution:
Application:
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)
}
Output:
from B
from A
from C
2
Macro implementation:
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)
}
}
+2
source to share