Supposons que nous voulions écrire une macro qui définit une classe anonyme avec des membres de type ou des méthodes, puis crée une instance de cette classe qui est typée statiquement comme un type structurel avec ces méthodes, etc. Ceci est possible avec le système de macros de la version 2.10. 0, et la partie membre de type est extrêmement simple:
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)
))
}
}
(Où ReflectionUtils
est un trait de commodité qui fournit ma constructor
méthode.)
Cette macro nous permet de spécifier le nom du membre de type de la classe anonyme sous forme de chaîne littérale:
scala> MacroExample.foo("T")
res0: AnyRef{type T = Int} = $1$$1@7da533f6
Notez qu'il est correctement saisi. Nous pouvons confirmer que tout fonctionne comme prévu:
scala> implicitly[res0.T =:= Int]
res1: =:=[res0.T,Int] = <function1>
Supposons maintenant que nous essayions de faire la même chose avec une méthode:
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)
))
}
Mais lorsque nous l'essayons, nous n'obtenons pas de type structurel:
scala> MacroExample.bar("test")
res1: AnyRef = $1$$1@da12492
Mais si nous y collons une classe anonyme supplémentaire:
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)
))
}
Ça marche:
scala> MacroExample.baz("test")
res0: AnyRef{def test: Int} = $2$$1@6663f834
scala> res0.test
res1: Int = 42
C'est extrêmement pratique - cela vous permet de faire des choses comme celle-ci , par exemple - mais je ne comprends pas pourquoi cela fonctionne, et la version du membre de type fonctionne, mais pas bar
. Je sais que ce n'est peut-être pas un comportement défini , mais cela a-t-il un sens? Existe-t-il un moyen plus propre d'obtenir un type structurel (avec les méthodes dessus) à partir d'une macro?