Uzyskiwanie typu strukturalnego za pomocą metod anonimowej klasy z makra

181

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 ReflectionUtilsjest cecha wygody, która zapewnia moją constructormetodę).

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?

Travis Brown
źródło
14
Co ciekawe, jeśli napiszesz ten sam kod w REPL zamiast generować go w makrze, działa on: scala> {final class anon {def x = 2}; new anon} res1: AnyRef {def x: Int} = anon 1 $ @ 5295c398. Dziękuję za raport! Rzucę okiem w tym tygodniu.
Eugene Burmako,
1
Pamiętaj, że zgłosiłem tutaj problem .
Travis Brown,
Nie, nie jest to bloker, dziękuję - dodatkowa anonimowa sztuczka klasowa działała dla mnie, ilekroć jej potrzebowałem. Właśnie zauważyłem kilka pozytywnych opinii na pytanie i byłem ciekawy statusu.
Travis Brown,
3
typ element członkowski jest niezwykle łatwy -> wTF? jesteś niesamowicie crack! na dobry sposób oczywiście
ZaoTaoBao
3
Jest tutaj 153 głosów pozytywnych i tylko 1 za problem na scala-lang.org . Więcej pozytywnych opinii może szybciej rozwiązać problem?
moodboom

Odpowiedzi:

9

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.

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)
  }
}
som-snytt
źródło
2
Po prostu zauważę, że tak naprawdę przedstawiam pierwsze obejście tego pytania (tutaj nie jest to quasi-cytowane tutaj). Cieszę się, że ta odpowiedź kończy pytanie - myślę, że niejasno czekałem na naprawienie błędu.
Travis Brown,
@TravisBrown Założę się, że masz także inne narzędzia w Pasie Bat. Dzięki za heads-upy: założyłem, że twój AST to „stara sztuczka z dodatkowymi nawiasami klamrowymi”, ale teraz widzę, że ClassDef / Apply nie są zawinięte we własny blok, jak to się dzieje new $anon {}. Moją drugą anonwadą jest to, że w przyszłości nie będę używać makr z quasi-cytatami lub podobnymi nazwami specjalnymi.
som-snytt
q Składnia „$ {s: String}” jest nieco opóźniona, szczególnie jeśli korzystasz z raju. Więc bardziej jak w przyszłym miesiącu niż w przyszłym tygodniu.
Denys Shabalin,
@ som-snytt @ denys-shabalin, czy istnieje specjalny rodzaj oszustwa dla typów strukturalnych a-la shapeless.Generic? Pomimo moich najlepszych intencji, aby wymusić Auxtypy powrotu wzoru, kompilator odmawia przejrzenia typu strukturalnego.
Flavian