wake-up-neo.com

Abrufen eines Strukturtyps mit den Methoden einer anonymen Klasse aus einem Makro

Angenommen, wir möchten ein Makro schreiben, das eine anonyme Klasse mit einigen Typelementen oder Methoden definiert, und dann eine Instanz dieser Klasse erstellen, die mit diesen Methoden usw. statisch als Strukturtyp typisiert ist. Dies ist mit dem Makrosystem in 2.10 möglich. 0, und der Typmitgliedteil ist extrem einfach:

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)
    ))
  }
}

(Wobei ReflectionUtils ein Bequemlichkeitsmerkmal ist, das meine constructor -Methode bereitstellt.)

Mit diesem Makro können wir den Namen des Typmitglieds der anonymen Klasse als Zeichenfolgenliteral angeben:

scala> MacroExample.foo("T")
res0: AnyRef{type T = Int} = [email protected]

Beachten Sie, dass es richtig geschrieben ist. Wir können bestätigen, dass alles wie erwartet funktioniert:

scala> implicitly[res0.T =:= Int]
res1: =:=[res0.T,Int] = <function1>

Nehmen wir nun an, dass wir versuchen, dasselbe mit einer Methode zu tun:

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)
  ))
}

Aber wenn wir es ausprobieren, bekommen wir keinen Strukturtyp:

scala> MacroExample.bar("test")
res1: AnyRef = [email protected]

Aber wenn wir eine extra anonyme Klasse da reinstecken:

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)
  ))
}

Es klappt:

scala> MacroExample.baz("test")
res0: AnyRef{def test: Int} = [email protected]

scala> res0.test
res1: Int = 42

Dies ist äußerst praktisch - Sie können zum Beispiel Dinge wie this ausführen -, aber ich verstehe nicht, warum es funktioniert und die Typ-Member-Version funktioniert, aber nicht bar. Ich weiß, dass möglicherweise nicht definiertes Verhalten , aber macht es irgendeinen Sinn? Gibt es eine sauberere Möglichkeit, einen Strukturtyp (mit den Methoden darauf) aus einem Makro abzurufen?

182
Travis Brown

Diese Frage wird in zweifacher Ausfertigung von Travis hier beantwortet. Es gibt Links zum Thema im Tracker und zur Diskussion von Eugene (in den Kommentaren und in der Mailingliste).

In der berühmten Sektion "Skylla und Charybdis" des Type Checkers entscheidet unser Held, was der dunklen Anonymität entgehen und das Licht als Mitglied des Strukturtyps sehen soll.

Es gibt verschiedene Möglichkeiten, den Typ Checker auszutricksen (was nicht Odysseus 'Trick beinhaltet, ein Schaf zu umarmen). Am einfachsten ist es, eine Dummy-Anweisung einzufügen, damit der Block nicht wie eine anonyme Klasse aussieht, auf die die Instanziierung folgt.

Wenn der Typer feststellt, dass Sie ein öffentlicher Begriff sind, auf den von außen nicht verwiesen wird, werden Sie privat.

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)
  }
}
9
som-snytt