Ottenere un tipo strutturale con metodi di una classe anonima da una macro


181

Supponiamo di voler scrivere una macro che definisca una classe anonima con alcuni membri o metodi di tipo e quindi crei un'istanza di quella classe che è staticamente digitata come tipo strutturale con quei metodi, ecc. Questo è possibile con il sistema macro in 2.10. 0 e la parte membro del tipo è estremamente semplice:

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

(Dov'è ReflectionUtilsun tratto di convenienza che fornisce il mio constructormetodo.)

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

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

Si noti che è opportunamente digitato. Possiamo confermare che tutto funziona come previsto:

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

Supponiamo ora che proviamo a 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 lo proviamo, non otteniamo un tipo strutturale:

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

Ma se inseriamo una classe anonima in più:

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} = $2$$1@6663f834

scala> res0.test
res1: Int = 42

Questo è estremamente utile — ti consente di fare cose come questa , per esempio — ma non capisco perché funzioni, e la versione di tipo membro funziona, ma non bar. So che questo potrebbe non essere un comportamento definito , ma ha senso? Esiste 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 generarlo in una macro, funziona: scala> {final class anon {def x = 2}; nuovo anon} res1: AnyRef {def x: Int} = anon $ 1 @ 5295c398. Grazie per la segnalazione! Daro 'un'occhiata questa settimana.
Eugene Burmako,

1
Nota che ho presentato un problema qui .
Travis Brown,

No, non un blocco, grazie: il trucco extra anonimo di classe ha funzionato per me ogni volta che ne avevo bisogno. Ho appena notato un paio di voti positivi sulla domanda ed ero curioso di sapere lo stato.
Travis Brown,

3
la parte del tipo di membro è estremamente semplice -> wTF? sei estremamente crack! nel buon modo ovviamente :)
ZaoTaoBao

3
Ci sono 153 voti qui, e solo 1 per il problema su scala-lang.org . Più voti lì potrebbero risolverlo più velocemente?
moodboom

Risposte:


9

A questa domanda risponde in duplice copia Travis qui . 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 controllo del tipo, il nostro eroe decide cosa deve sfuggire all'anonimato oscuro e vede la luce come un membro del tipo strutturale.

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

Se il typer nota che sei un termine pubblico a cui non fa riferimento l'esterno, ti renderà 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)
  }
}

2
Noterò solo che in realtà fornisco la prima soluzione alternativa in questa domanda stessa (è solo poco citata qui). Sono felice di avere questa risposta che racchiuda la domanda: penso di aver vagamente aspettato che il bug venisse risolto.
Travis Brown,

@TravisBrown Scommetto che hai anche altri strumenti nella tua Bat Belt. Grazie per l'heads-up: ho pensato che il tuo AST fosse "il vecchio trucco delle parentesi graffe", ma ora vedo che ClassDef / Apply non sono avvolti nel loro blocco, come accade con new $anon {}. L'altro mio take-away è che in futuro non userò anonin macro con quasiquote o nomi speciali simili.
som-snytt

q La sintassi "$ {s: String}" viene leggermente ritardata, specialmente se stai usando paradise. Quindi più come il prossimo mese piuttosto che la prossima settimana.
Denys Shabalin,

@ som-snytt @ denys-shabalin, esiste un tipo speciale di inganno per i tipi strutturali a-la shapeless.Generic? Nonostante le mie migliori intenzioni di forzare i Auxtipi di ritorno del modello, il compilatore rifiuta di vedere attraverso il tipo strutturale.
flavian
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.