Отримання структурного типу з методами анонімного класу з макросу


181

Припустимо, ми хочемо написати макрос, який визначає анонімний клас з деякими членами типу або методами, а потім створює екземпляр цього класу, який статично набраний як структурний тип із цими методами тощо. Це можливо для макросистеми в 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}; новий anon} res1: AnyRef {def x: Int} = anon $ 1 @ 5295c398. Дякуємо за звіт! Я огляну цей тиждень.
Євген Бурмако

1
Зауважте, що я тут подав питання .
Тревіс Браун

Ні, не блокатор, дякую - зайвий анонімний трюк класу працював для мене, коли мені це було потрібно. Я щойно помітив пару обґрунтованих запитань у питанні та цікавився статусу.
Тревіс Браун

3
тип члена частина надзвичайно проста -> wTF? Ви вкрай
зламаєтеся

3
Тут є 153 оновлення, і лише 1 для випуску на сайті scala-lang.org . Більше об'яв може бути вирішено швидше?
moodboom

Відповіді:


9

На це запитання Travis відповів у двох примірниках тут . Є посилання на проблему в трекері та на обговоренні Євгенія (у списку коментарів та розсилки).

У відомому розділі перевірки типу «Скілла і Харибдіс» наш герой вирішує, що слід уникати темної анонімності і бачити світло як член структурного типу.

Існує кілька способів підманути перевірку типу (які не тягнуть за собою Одіссея хитрість обіймати вівцю). Найпростіший - вставити фіктивний вислів, щоб блок не був схожим на анонімний клас з подальшим його встановленням.

Якщо друкар помітить, що ви є державним терміном, на який зовні не посилаються, це зробить вас приватними.

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
Я лише зазначу, що я фактично даю перше вирішення в цьому питанні (це просто некваліфіковано тут). Я щасливий, що ця відповідь завершила питання - я думаю, я нечітко чекала, коли помилка виправиться.
Тревіс Браун

@TravisBrown Сподіваюся, у ваших поясів також є інші інструменти. Thx для голови вгору: Я припустив, що ваш AST був "старим трюком додаткових брекетів", але зараз я бачу, що ClassDef / Apply не загорнуті у власний блок, як це відбувається new $anon {}. Мій інший винос полягає в тому, що в майбутньому я не буду використовувати anonв макросах квазіквітів або подібних спеціальних імен.
som-snytt

q "$ {s: String}" синтаксис трохи затримується, особливо якщо ви використовуєте рай. Так більше подобається наступний місяць, а не наступний тиждень.
Денис Шабалін

@ som-snytt @ denys-shabalin, чи існує особливий вид хитрості для структурних типів а-ля shapeless.Generic? Незважаючи на мої найкращі наміри форсувати Auxтипи повернення шаблону, компілятор відмовляється бачити структурний тип.
флавіан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.