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?
la source
Réponses:
Cette question est répondue en double par Travis ici . Il y a des liens vers le problème dans le tracker et vers la discussion d'Eugene (dans les commentaires et la liste de diffusion).
Dans la célèbre section "Skylla et Charybdis" du vérificateur de types, notre héros décide de ce qui échappera à l'anonymat sombre et verra la lumière comme un membre du type structurel.
Il y a deux façons de tromper le vérificateur de type (qui n'impliquent pas le stratagème d'Ulysse consistant à serrer un mouton dans ses bras). Le plus simple est d'insérer une instruction factice pour que le bloc ne ressemble pas à une classe anonyme suivie de son instanciation.
Si le rédacteur remarque que vous êtes un terme public qui n'est pas référencé par l'extérieur, cela vous rendra privé.
la source
new $anon {}
. Mon autre pointanon
à retenir est qu'à l'avenir, je ne les utiliserai plus dans les macros avec des quasiquotes ou des noms spéciaux similaires.shapeless.Generic
? Malgré mes meilleures intentions de forcer lesAux
types de retour de modèle, le compilateur refuse de voir à travers le type structurel.