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

180

Предположим, мы хотим написать макрос, который определяет анонимный класс с некоторыми членами или методами типа, а затем создает экземпляр этого класса, который статически вводится как структурный тип с этими методами и т.д. Это возможно с помощью макросистемы в 2.10.0, а часть элемента элемента очень проста:

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
    Обратите внимание, что я подал проблему здесь .
Показать ещё 6 комментариев
Теги:
macros
structural-typing
scala-macros
scala-2.10

1 ответ

8
Лучший ответ

Этот вопрос ответил в двух экземплярах Travis здесь. В трекере есть ссылки на эту проблему и обсуждение в Eugene (в комментариях и списке рассылки).

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

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

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

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)
  }
}
  • 2
    Я просто отмечу, что на самом деле я предоставляю первый обходной путь в этом вопросе (здесь он просто не квазицитирован). Я рад, что этот ответ обернул вопрос - думаю, я смутно ждал, когда ошибка будет исправлена.
  • 0
    @TravisBrown Могу поспорить, у вас есть и другие инструменты в вашем Bat Belt. Спасибо за внимание: я предположил, что ваш AST был «старым трюком с дополнительными скобками», но теперь я вижу, что ClassDef / Apply не заключены в их собственный блок, как это происходит с new $anon {} . Мой другой вывод заключается в том, что в будущем я не буду использовать anon в макросах с квазицитатами или аналогичными специальными именами.
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню