Suponha que desejemos escrever uma macro que defina uma classe anônima com alguns membros ou métodos de tipo e, em seguida, crie uma instância dessa classe que seja estaticamente digitada como um tipo estrutural com esses métodos, etc. Isso é possível com o sistema de macro na versão 2.10. 0 e a parte do membro de tipo é extremamente 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)
))
}
}
(Onde ReflectionUtils
está um traço de conveniência que fornece meu constructor
método.)
Essa macro nos permite especificar o nome do membro do tipo da classe anônima como uma literal de cadeia de caracteres:
scala> MacroExample.foo("T")
res0: AnyRef{type T = Int} = $1$$1@7da533f6
Observe que foi digitado adequadamente. Podemos confirmar que tudo está funcionando como esperado:
scala> implicitly[res0.T =:= Int]
res1: =:=[res0.T,Int] = <function1>
Agora, suponha que tentemos fazer a mesma coisa com um 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)
))
}
Mas quando tentamos, não temos um tipo estrutural:
scala> MacroExample.bar("test")
res1: AnyRef = $1$$1@da12492
Mas se colocarmos uma classe anônima extra 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)
))
}
Funciona:
scala> MacroExample.baz("test")
res0: AnyRef{def test: Int} = $2$$1@6663f834
scala> res0.test
res1: Int = 42
Isso é extremamente útil - permite fazer coisas como essa , por exemplo - mas não entendo por que funciona e a versão do membro de tipo funciona, mas não bar
. Eu sei que isso pode não ser um comportamento definido , mas isso faz algum sentido? Existe uma maneira mais limpa de obter um tipo estrutural (com os métodos nele) a partir de uma macro?