Получение структурного типа с помощью методов анонимного класса из макроса

181

Предположим, мы хотим написать макрос, который определяет анонимный класс с некоторыми типами-членами или методами, а затем создает экземпляр этого класса, который статически типизируется как структурный тип с этими методами и т. Д. Это возможно с помощью системы макросов в 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. Я знаю, что это не может быть определено поведение , но имеет ли это смысл? Есть ли более чистый способ получить структурный тип (с методами на нем) из макроса?

Трэвис Браун
источник
14
Интересно, что если вы пишете тот же код в REPL, а не генерируете его в макросе, он работает: scala> {final class anon {def x = 2}; new anon} res1: AnyRef {def x: Int} = anon $ 1 @ 5295c398. Спасибо за отчет! Я посмотрю на этой неделе.
Евгений Бурмако
1
Обратите внимание, что я подал проблему здесь .
Трэвис Браун
Нет, не блокировщик, спасибо - дополнительная уловка анонимного класса работала для меня всякий раз, когда я нуждался в этом. Я только заметил пару голосов по этому вопросу, и мне было интересно узнать о его статусе.
Трэвис Браун
3
Тип членской части чрезвычайно прост -> wTF? Вы очень крутые! в хорошем смысле слова конечно :)
ZaoTaoBao
3
Здесь 153 отзыва, и только 1 за проблему на scala-lang.org . Чем больше голосов может решить, тем быстрее?
moodboom

Ответы:

9

На этот вопрос Трэвис отвечает в двух экземплярах здесь . Есть ссылки на проблему в трекере и на обсуждение Евгения (в комментариях и списке рассылки).

В знаменитом разделе «Skylla and Charybdis» программы проверки типов наш герой решает, что следует избегать темной анонимности и видеть свет как члена структурного типа.

Есть несколько способов обмануть проверку типов (которые не влекут за собой уловку Одиссея обнимать овец). Самое простое - вставить фиктивный оператор, чтобы блок не выглядел как анонимный класс с последующей его реализацией.

Если тидер замечает, что вы общедоступный термин, на который не ссылаются извне, это сделает вас личным.

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)
  }
}
сома-snytt
источник
2
Я только отмечу, что я фактически предоставляю первый обходной путь в этом вопросе сам (это просто не квазицитировано здесь). Я рад, что этот ответ обернул вопрос - думаю, я смутно ждал, когда ошибка будет исправлена.
Трэвис Браун
@TravisBrown Могу поспорить, у вас есть и другие инструменты в вашем Bat Belt. Спасибо за внимание: я предположил, что ваш AST был «старым трюком с дополнительными скобками», но теперь я вижу, что ClassDef / Apply не заключены в их собственный блок, как это происходит с new $anon {}. Другой мой anonвывод: в будущем я не буду использовать в макросах квазицитаты или аналогичные специальные имена.
Som-Snytt
Синтаксис "$ {s: String}" немного задерживается, особенно если вы используете Paradise. Так что больше похоже на следующий месяц, а не на следующую неделю.
Денис Шабалин
@ som-snytt @ denys-shabalin, а-а-а есть специальные хитрости для структурных типов shapeless.Generic? Несмотря на мои лучшие намерения форсировать Auxтипы возвращаемых данных, компилятор отказывается видеть структурный тип.
Flavian