182 votes

L'obtention d'un type de structure avec une classe anonyme méthodes à partir d'une macro

Supposons que nous voulions écrire une macro qui définit une classe anonyme avec un certain type de membres ou de ses méthodes et crée une instance de cette classe qui est statiquement typé comme un type de construction avec ces méthodes, etc. C'est possible avec le système de macro dans 2.10.0, et le type de membre partie est extrêmement facile:

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

(Où ReflectionUtils est une commodité trait de caractère qui m' constructor méthode.)

Cette macro permet de préciser le nom de la classe anonyme de type militaire, comme une chaîne de caractères littérale:

scala> MacroExample.foo("T")
res0: AnyRef{type T = Int} = $1$$1@7da533f6

Notez qu'il est correctement saisi. Nous pouvons confirmer que tout fonctionne comme prévu:

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

Maintenant, supposons que nous essayons de faire la même chose avec une méthode:

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

Mais quand nous essayons, nous ne sommes pas un type de structure:

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

Mais si nous nous en tenons un supplément de classe anonyme là:

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

Il fonctionne:

scala> MacroExample.baz("test")
res0: AnyRef{def test: Int} = $2$$1@6663f834

scala> res0.test
res1: Int = 42

C'est très pratique,-il vous permet de faire des choses comme cela, par exemple-mais je ne comprends pas pourquoi il fonctionne, et le type de membre version fonctionne, mais pas bar. Je sais que cela ne peut pas être définie comportement, mais sera-t-elle un sens? Est-il le moyen le plus propre à obtenir un type de structure (les méthodes) à partir d'une macro?

10voto

som-snytt Points 17224

Cette question est répondue en double exemplaire par Travis ici. Il y a des liens à la question dans le dispositif de suivi et d'Eugène de discussion (dans les commentaires et liste de diffusion).

Dans le célèbre "Skylla et Scylla" du type de contrôleur, notre héros décide de ce qui doit échapper à la sombre de l'anonymat et de voir la lumière en tant que membre du type de structure.

Il ya un couple de façons de tromper le type de correcteur (qui n'entraînent pas d'Ulysse stratagème de serrant un mouton). Le plus simple est d'insérer une fausse déclaration, de sorte que le bloc ne ressemble pas à une classe anonyme, suivie par son instanciation.

Si le typer les avis que vous êtes un public, terme qui n'est pas référencé par l'extérieur, il vous en privé.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X