Angenommen, wir möchten ein Makro schreiben, das eine anonyme Klasse mit einigen Typelementen oder Methoden definiert, und dann eine Instanz dieser Klasse erstellen, die mit diesen Methoden usw. statisch als Strukturtyp typisiert ist. Dies ist mit dem Makrosystem in 2.10 möglich. 0, und das Typelementteil ist extrem einfach:
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)
))
}
}
(Wo ReflectionUtils
ist ein Convenience-Merkmal , das meine constructor
Methode bereitstellt? )
Mit diesem Makro können wir den Namen des Typmitglieds der anonymen Klasse als Zeichenfolgenliteral angeben:
scala> MacroExample.foo("T")
res0: AnyRef{type T = Int} = $1$$1@7da533f6
Beachten Sie, dass es entsprechend eingegeben ist. Wir können bestätigen, dass alles wie erwartet funktioniert:
scala> implicitly[res0.T =:= Int]
res1: =:=[res0.T,Int] = <function1>
Nehmen wir nun an, wir versuchen dasselbe mit einer Methode zu tun:
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)
))
}
Aber wenn wir es ausprobieren, bekommen wir keinen strukturellen Typ:
scala> MacroExample.bar("test")
res1: AnyRef = $1$$1@da12492
Aber wenn wir eine zusätzliche anonyme Klasse da reinstecken:
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)
))
}
Es klappt:
scala> MacroExample.baz("test")
res0: AnyRef{def test: Int} = $2$$1@6663f834
scala> res0.test
res1: Int = 42
Dies ist äußerst handlich-it können Sie Dinge wie tun dies , zum Beispiel , aber ich verstehe nicht , warum es funktioniert, und die Typ - Member - Version funktioniert, aber nicht bar
. Ich weiß, dass dies möglicherweise kein definiertes Verhalten ist , aber macht es irgendeinen Sinn? Gibt es eine sauberere Möglichkeit, einen Strukturtyp (mit den darauf enthaltenen Methoden) aus einem Makro zu erhalten?