Предположим, мы хотим написать макрос, который определяет анонимный класс с некоторыми типами-членами или методами, а затем создает экземпляр этого класса, который статически типизируется как структурный тип с этими методами и т. Д. Это возможно с помощью системы макросов в 2.10. 0, а часть типа type очень проста:
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)
))
}
}
(Где ReflectionUtils
это удобство черта , которая обеспечивает мой constructor
метод.)
Этот макрос позволяет нам указать имя члена типа анонимного класса в виде строкового литерала:
scala> MacroExample.foo("T")
res0: AnyRef{type T = Int} = $1$$1@7da533f6
Обратите внимание, что он правильно напечатан. Мы можем подтвердить, что все работает, как ожидалось:
scala> implicitly[res0.T =:= Int]
res1: =:=[res0.T,Int] = <function1>
Теперь предположим, что мы пытаемся сделать то же самое с методом:
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)
))
}
Но когда мы пробуем это, мы не получаем структурный тип:
scala> MacroExample.bar("test")
res1: AnyRef = $1$$1@da12492
Но если мы добавим туда дополнительный анонимный класс:
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)
))
}
Оно работает:
scala> MacroExample.baz("test")
res0: AnyRef{def test: Int} = $2$$1@6663f834
scala> res0.test
res1: Int = 42
Это очень удобно, это позволяет делать такие вещи , как это , например, но я не понимаю , почему это работает, и версия работы члены типа, но не bar
. Я знаю, что это не может быть определено поведение , но имеет ли это смысл? Есть ли более чистый способ получить структурный тип (с методами на нем) из макроса?
источник
Ответы:
На этот вопрос Трэвис отвечает в двух экземплярах здесь . Есть ссылки на проблему в трекере и на обсуждение Евгения (в комментариях и списке рассылки).
В знаменитом разделе «Skylla and Charybdis» программы проверки типов наш герой решает, что следует избегать темной анонимности и видеть свет как члена структурного типа.
Есть несколько способов обмануть проверку типов (которые не влекут за собой уловку Одиссея обнимать овец). Самое простое - вставить фиктивный оператор, чтобы блок не выглядел как анонимный класс с последующей его реализацией.
Если тидер замечает, что вы общедоступный термин, на который не ссылаются извне, это сделает вас личным.
источник
new $anon {}
. Другой мойanon
вывод: в будущем я не буду использовать в макросах квазицитаты или аналогичные специальные имена.shapeless.Generic
? Несмотря на мои лучшие намерения форсироватьAux
типы возвращаемых данных, компилятор отказывается видеть структурный тип.