Załóżmy, że chcemy napisać makro, które definiuje anonimową klasę z niektórymi typami elementów lub metodami, a następnie tworzy instancję tej klasy, która jest statycznie typowana jako typ strukturalny za pomocą tych metod itp. Jest to możliwe w przypadku systemu makr w 2.10. 0, a część składowa typu jest niezwykle łatwa:
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)
))
}
}
(Gdzie ReflectionUtils
jest cecha wygody, która zapewnia moją constructor
metodę).
To makro pozwala nam określić nazwę elementu typu anonimowej klasy jako dosłowny ciąg znaków:
scala> MacroExample.foo("T")
res0: AnyRef{type T = Int} = $1$$1@7da533f6
Pamiętaj, że jest odpowiednio wpisany. Możemy potwierdzić, że wszystko działa zgodnie z oczekiwaniami:
scala> implicitly[res0.T =:= Int]
res1: =:=[res0.T,Int] = <function1>
Załóżmy teraz, że próbujemy zrobić to samo za pomocą metody:
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)
))
}
Ale kiedy go wypróbujemy, nie otrzymujemy typu strukturalnego:
scala> MacroExample.bar("test")
res1: AnyRef = $1$$1@da12492
Ale jeśli wstawimy tam dodatkową anonimową klasę:
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)
))
}
To działa:
scala> MacroExample.baz("test")
res0: AnyRef{def test: Int} = $2$$1@6663f834
scala> res0.test
res1: Int = 42
Jest to bardzo przydatna, pozwala zrobić rzeczy jak to , na przykład, ale nie rozumiem, dlaczego to działa, a prace wersja członkowskie typ, ale nie bar
. Wiem, że to nie może być określone zachowanie , ale czy ma to jakiś sens? Czy istnieje prostszy sposób na uzyskanie typu strukturalnego (z metodami na nim) z makra?
źródło
Odpowiedzi:
Travis udzielił odpowiedzi w dwóch egzemplarzach tutaj . Są linki do problemu w module śledzącym i do dyskusji Eugene'a (w komentarzach i liście mailingowej).
W słynnej sekcji „Skylla i Charybdis” sprawdzania typu nasz bohater decyduje, co uniknie ciemnej anonimowości i zobaczy światło jako element typu strukturalnego.
Istnieje kilka sposobów na oszukanie sprawdzania typu (które nie pociągają za sobą podstępu Odyseusza do przytulenia owiec). Najprostszym jest wstawienie fikcyjnej instrukcji, aby blok nie wyglądał jak anonimowa klasa, po której następuje jej utworzenie.
Jeśli typer zauważy, że jesteś terminem publicznym, na który nie ma odniesienia z zewnątrz, spowoduje to, że będziesz prywatny.
źródło
new $anon {}
. Moją drugąanon
wadą jest to, że w przyszłości nie będę używać makr z quasi-cytatami lub podobnymi nazwami specjalnymi.shapeless.Generic
? Pomimo moich najlepszych intencji, aby wymusićAux
typy powrotu wzoru, kompilator odmawia przejrzenia typu strukturalnego.