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

181

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

(Wo ReflectionUtilsist ein Convenience-Merkmal , das meine constructorMethode 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} = $1$$1@7da533f6

Beachten Sie, dass es entsprechend eingegeben 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, 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 strukturellen Typ:

scala> MacroExample.bar("test")
res1: AnyRef = $1$$1@da12492

Aber wenn wir eine zusätzliche 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} = $2$$1@6663f834

scala> res0.test
res1: Int = 42

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

Travis Brown
quelle
14
Interessanterweise funktioniert es, wenn Sie denselben Code in REPL schreiben, anstatt ihn in einem Makro zu generieren: scala> {final class anon {def x = 2}; new anon} res1: AnyRef {def x: Int} = anon $ 1 @ 5295c398. Danke für den Bericht! Ich werde diese Woche einen Blick darauf werfen.
Eugene Burmako
1
Beachten Sie, dass ich hier ein Problem eingereicht habe .
Travis Brown
Nein, kein Blocker, danke - der zusätzliche anonyme Klassentrick hat bei mir funktioniert, wann immer ich ihn brauchte. Ich habe gerade ein paar positive Stimmen zu dieser Frage bemerkt und war neugierig auf den Status.
Travis Brown
3
Typ Mitgliedsteil ist extrem einfach -> wTF? Sie sind extrem
verrückt
3
Hier gibt es 153 positive Stimmen und nur eine für das Problem auf scala-lang.org . Weitere Upvotes dort könnten es schneller lösen?
Moodboom

Antworten:

9

Diese Frage wird von Travis hier doppelt beantwortet . Es gibt Links zu dem Problem im Tracker und zu Eugenes Diskussion (in den Kommentaren und in der Mailingliste).

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

Es gibt verschiedene Möglichkeiten, den Typprüfer 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, gefolgt von seiner Instanziierung.

Wenn der Typer bemerkt, 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)
  }
}
Som-Snytt
quelle
2
Ich werde nur bemerken, dass ich tatsächlich die erste Problemumgehung in dieser Frage selbst bereitstelle (sie ist hier nur nicht quasiquotiert). Ich bin froh, dass diese Antwort die Frage abschließt - ich glaube, ich hatte vage darauf gewartet, dass der Fehler behoben wird.
Travis Brown
@TravisBrown Ich wette, Sie haben auch andere Werkzeuge in Ihrem Fledermausgürtel. Danke für die Heads-Ups: Ich nahm an, dass Ihr AST "der alte Trick mit zusätzlichen Klammern" war, aber ich sehe jetzt, dass die ClassDef / Apply nicht wie in einem eigenen Block verpackt sind new $anon {}. Mein anderes Mitnehmen ist, dass ich es in Zukunft nicht mehr anonin Makros mit Quasiquoten oder ähnlichen speziellen Namen verwenden werde.
Som-Snytt
q Die Syntax "$ {s: String}" wird etwas verzögert, insbesondere wenn Sie das Paradies verwenden. Also eher wie im nächsten Monat als nächste Woche.
Denys Shabalin
@ som-snytt @ denys-shabalin, gibt es eine besondere Art von Trick für Strukturtypen a-la shapeless.Generic? Trotz meiner besten Absichten, Musterrückgabetypen zu erzwingen Aux, weigert sich der Compiler, den Strukturtyp zu durchschauen.
Flavian