Supponiamo di voler scrivere una macro che definisce una classe anonima con alcuni membri o metodi, e quindi crea un'istanza di quella classe che è tipizzata staticamente come un tipo strutturale con tali metodi, ecc Questo è possibile con il sistema di macro in 2.10.0, e la parte elemento tipo è estremamente semplice: (. Dove ReflectionUtils
è un convenience trait che fornisce il metodo constructor
)Ottenere un tipo strutturale con metodi di una classe anonima da una macro
object MacroExample extends ReflectionUtils {
import scala.language.experimental.macros
import scala.reflect.macros.Context
def foo(name: String): Any = macro foo_impl
def foo_impl(c: Context)(name: c.Expr[String]) = {
import c.universe._
val Literal(Constant(lit: String)) = name.tree
val anon = newTypeName(c.fresh)
c.Expr(Block(
ClassDef(
Modifiers(Flag.FINAL), anon, Nil, Template(
Nil, emptyValDef, List(
constructor(c.universe),
TypeDef(Modifiers(), newTypeName(lit), Nil, TypeTree(typeOf[Int]))
)
)
),
Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
))
}
}
Questa macro ci consente di specificare il nome del membro del tipo della classe anonima come una stringa letterale:
scala> MacroExample.foo("T")
res0: AnyRef{type T = Int} = [email protected]
Nota che è digitato correttamente. Possiamo confermare che il lavoro di tutto come previsto:
scala> implicitly[res0.T =:= Int]
res1: =:=[res0.T,Int] = <function1>
Ora supponiamo che cerchiamo di fare la stessa cosa con un metodo:
def bar(name: String): Any = macro bar_impl
def bar_impl(c: Context)(name: c.Expr[String]) = {
import c.universe._
val Literal(Constant(lit: String)) = name.tree
val anon = newTypeName(c.fresh)
c.Expr(Block(
ClassDef(
Modifiers(Flag.FINAL), anon, Nil, Template(
Nil, emptyValDef, List(
constructor(c.universe),
DefDef(
Modifiers(), newTermName(lit), Nil, Nil, TypeTree(),
c.literal(42).tree
)
)
)
),
Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
))
}
Ma quando cerchiamo fuori, non otteniamo un tipo strutturale:
scala> MacroExample.bar("test")
res1: AnyRef = [email protected]
Ma se ci atteniamo una classe in più anonima in là:
def baz(name: String): Any = macro baz_impl
def baz_impl(c: Context)(name: c.Expr[String]) = {
import c.universe._
val Literal(Constant(lit: String)) = name.tree
val anon = newTypeName(c.fresh)
val wrapper = newTypeName(c.fresh)
c.Expr(Block(
ClassDef(
Modifiers(), anon, Nil, Template(
Nil, emptyValDef, List(
constructor(c.universe),
DefDef(
Modifiers(), newTermName(lit), Nil, Nil, TypeTree(),
c.literal(42).tree
)
)
)
),
ClassDef(
Modifiers(Flag.FINAL), wrapper, Nil,
Template(Ident(anon) :: Nil, emptyValDef, constructor(c.universe) :: Nil)
),
Apply(Select(New(Ident(wrapper)), nme.CONSTRUCTOR), Nil)
))
}
Funziona:
scala> MacroExample.baz("test")
res0: AnyRef{def test: Int} = [email protected]
scala> res0.test
res1: Int = 42
Questo è estremamente maneggevole, che permette di fare le cose come this, per esempio, ma non capisco perché funziona, e la versione elemento tipo funziona, ma non bar
. Conosco questo may not be defined behavior, ma ha senso? C'è un modo più pulito per ottenere un tipo strutturale (con i metodi su di esso) da una macro?
È interessante notare che, se si scrive lo stesso codice in REPL invece di generare in una macro, funziona: scala> {anon final class {def x = 2}; new anon} res1: AnyRef {def x: Int} = anon $ 1 @ 5295c398. Grazie per la segnalazione! Daro un'occhiata questa settimana. –
Nota che ho archiviato un problema [qui] (https://issues.scala-lang.org/browse/SI-6992). –
No, non un blocco, grazie - il trucco di classe extra anonima ha funzionato per me ogni volta che ne avevo bisogno. Ho appena notato un paio di voti positivi sulla domanda ed ero curioso di conoscere lo stato. –