Supongamos que deseamos escribir una macro que defina una clase anónima con algunos miembros de tipo o métodos, y luego cree una instancia de esa clase que esté tipada estáticamente como un tipo estructural con esos métodos, etc. Esto es posible con el sistema de macros en 2.10. 0, y la parte del miembro tipo es extremadamente fácil:
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)
))
}
}
(¿Dónde ReflectionUtils
hay un rasgo de conveniencia que proporciona mi constructor
método?)
Esta macro nos permite especificar el nombre del miembro de tipo de la clase anónima como un literal de cadena:
scala> MacroExample.foo("T")
res0: AnyRef{type T = Int} = $1$$1@7da533f6
Tenga en cuenta que está escrito correctamente. Podemos confirmar que todo funciona como se esperaba:
scala> implicitly[res0.T =:= Int]
res1: =:=[res0.T,Int] = <function1>
Ahora supongamos que intentamos hacer lo mismo con un método:
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)
))
}
Pero cuando lo probamos, no obtenemos un tipo estructural:
scala> MacroExample.bar("test")
res1: AnyRef = $1$$1@da12492
Pero si colocamos una clase anónima adicional allí:
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)
))
}
Funciona:
scala> MacroExample.baz("test")
res0: AnyRef{def test: Int} = $2$$1@6663f834
scala> res0.test
res1: Int = 42
Esto es extremadamente útil, te permite hacer cosas como esta , por ejemplo, pero no entiendo por qué funciona, y la versión del miembro tipo funciona, pero no bar
. Sé que esto puede no ser un comportamiento definido , pero ¿tiene algún sentido? ¿Hay una forma más limpia de obtener un tipo estructural (con los métodos en él) de una macro?