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 der Typmitgliedteil 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)
))
}
}
(Wobei ReflectionUtils
ein Bequemlichkeitsmerkmal ist, 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} = [email protected]
Beachten Sie, dass es richtig geschrieben 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, dass 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 Strukturtyp:
scala> MacroExample.bar("test")
res1: AnyRef = [email protected]
Aber wenn wir eine extra 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} = [email protected]
scala> res0.test
res1: Int = 42
Dies ist äußerst praktisch - Sie können zum Beispiel Dinge wie this ausführen -, aber ich verstehe nicht, warum es funktioniert und die Typ-Member-Version funktioniert, aber nicht bar
. Ich weiß, dass möglicherweise nicht definiertes Verhalten , aber macht es irgendeinen Sinn? Gibt es eine sauberere Möglichkeit, einen Strukturtyp (mit den Methoden darauf) aus einem Makro abzurufen?
Diese Frage wird in zweifacher Ausfertigung von Travis hier beantwortet. Es gibt Links zum Thema im Tracker und zur Diskussion von Eugene (in den Kommentaren und in der Mailingliste).
In der berühmten Sektion "Skylla und Charybdis" des Type Checkers entscheidet unser Held, was der dunklen Anonymität entgehen und das Licht als Mitglied des Strukturtyps sehen soll.
Es gibt verschiedene Möglichkeiten, den Typ Checker auszutricksen (was nicht Odysseus 'Trick beinhaltet, ein Schaf zu umarmen). Am einfachsten ist es, eine Dummy-Anweisung einzufügen, damit der Block nicht wie eine anonyme Klasse aussieht, auf die die Instanziierung folgt.
Wenn der Typer feststellt, dass Sie ein öffentlicher Begriff sind, auf den von außen nicht verwiesen wird, werden Sie privat.
object Mac {
import scala.language.experimental.macros
import scala.reflect.macros.Context
/* Make an instance of a structural type with the named member. */
def bar(name: String): Any = macro bar_impl
def bar_impl(c: Context)(name: c.Expr[String]) = {
import c.universe._
val anon = TypeName(c.freshName)
// next week, val q"${s: String}" = name.tree
val Literal(Constant(s: String)) = name.tree
val A = TermName(s)
val dmmy = TermName(c.freshName)
val tree = q"""
class $anon {
def $A(i: Int): Int = 2 * i
}
val $dmmy = 0
new $anon
"""
// other ploys
//(new $anon).asInstanceOf[{ def $A(i: Int): Int }]
// reference the member
//val res = new $anon
//val $dmmy = res.$A _
//res
// the canonical ploy
//new $anon { } // braces required
c.Expr(tree)
}
}