2013-01-17 7 views
180

Supponiamo di voler scrivere una macro che definisce una classe anonima con alcuni membri o metodi, e quindi crea un'istanza di quella classe che è tipizzata staticamente come un tipo strutturale con tali metodi, ecc Questo è possibile con il sistema di macro in 2.10.0, e la parte elemento tipo è estremamente semplice: (. Dove ReflectionUtils è un convenience trait che fornisce il metodo constructor)Ottenere un tipo strutturale con metodi di una classe anonima da una macro

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

Questa macro ci consente di specificare il nome del membro del tipo della classe anonima come una stringa letterale:

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

Nota che è digitato correttamente. Possiamo confermare che il lavoro di tutto come previsto:

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

Ora supponiamo che cerchiamo di fare la stessa cosa con un metodo:

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

Ma quando cerchiamo fuori, non otteniamo un tipo strutturale:

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

Ma se ci atteniamo una classe in più anonima in 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) 
)) 
} 

Funziona:

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

scala> res0.test 
res1: Int = 42 

Questo è estremamente maneggevole, che permette di fare le cose come this, per esempio, ma non capisco perché funziona, e la versione elemento tipo funziona, ma non bar. Conosco questo may not be defined behavior, ma ha senso? C'è un modo più pulito per ottenere un tipo strutturale (con i metodi su di esso) da una macro?

+14

È interessante notare che, se si scrive lo stesso codice in REPL invece di generare in una macro, funziona: scala> {anon final class {def x = 2}; new anon} res1: AnyRef {def x: Int} = anon $ 1 @ 5295c398. Grazie per la segnalazione! Daro un'occhiata questa settimana. –

+1

Nota che ho archiviato un problema [qui] (https://issues.scala-lang.org/browse/SI-6992). –

+0

No, non un blocco, grazie - il trucco di classe extra anonima ha funzionato per me ogni volta che ne avevo bisogno. Ho appena notato un paio di voti positivi sulla domanda ed ero curioso di conoscere lo stato. –

risposta

8

La risposta è una risposta in doppio da Travis here. Ci sono collegamenti al problema nel tracker e alla discussione di Eugene (nei commenti e nella mailing list).

Nella famosa sezione "Skylla e Charybdis" del controllore del tipo, il nostro eroe decide cosa deve sfuggire all'anonimato scuro e vedere la luce come un membro del tipo strutturale.

Ci sono un paio di modi per ingannare il correttore di tipi (che non comportano lo stratagemma di Odysseus di abbracciare una pecora). Il più semplice è inserire un'istruzione fittizia in modo che il blocco non assomigli ad una classe anonima seguita dalla sua istanza.

Se il typer rileva che si è un termine pubblico a cui non fa riferimento l'esterno, esso diventerà privato.

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

Mi limiterò a notare che in realtà fornisco la prima soluzione alternativa a questa domanda (è solo una-quasiquote qui). Sono felice di avere questa risposta per riassumere la domanda: penso che stavo vagamente aspettando che il bug venisse corretto. –

+0

@TravisBrown Scommetto che anche tu hai altri strumenti nella tua Bat Belt. Thx for the heads up: ho pensato che il tuo AST fosse "il vecchio trucco delle parentesi extra", ma ora vedo che ClassDef/Apply non sono avvolti nel loro Block, come succede con 'new $ anon {}'. Il mio altro take-away è che in futuro non userò 'anon' in macro con quasiquotes o nomi speciali simili. –

+0

q La sintassi "$ {s: String}" viene leggermente ritardata, specialmente se si utilizza il paradiso. Quindi più come il prossimo mese piuttosto che la prossima settimana. –

Problemi correlati