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?
quelle
Antworten:
Diese Frage wird von Travis hier doppelt beantwortet . Es gibt Links zu dem Problem im Tracker und zu Eugenes Diskussion (in den Kommentaren und in der Mailingliste).
In der berühmten Sektion "Skylla und Charybdis" des Typprüfers entscheidet unser Held, was der dunklen Anonymität entgehen und das Licht als Mitglied des Strukturtyps sehen soll.
Es gibt verschiedene Möglichkeiten, den Typprüfer 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, gefolgt von seiner Instanziierung.
Wenn der Typer bemerkt, dass Sie ein öffentlicher Begriff sind, auf den von außen nicht verwiesen wird, werden Sie privat.
quelle
new $anon {}
. Mein anderes Mitnehmen ist, dass ich es in Zukunft nicht mehranon
in Makros mit Quasiquoten oder ähnlichen speziellen Namen verwenden werde.shapeless.Generic
? Trotz meiner besten Absichten, Musterrückgabetypen zu erzwingenAux
, weigert sich der Compiler, den Strukturtyp zu durchschauen.