2012-06-14 17 views
8

Estoy desarrollando una DSL y estoy obteniendo un error de "término libre" al expandir una macro. Me gustaría saber si se puede evitar. He simplificado el problema a la siguiente situación.¿Se puede evitar este error de variable de término libre (producido en la macro expansión)?

Supongamos que tenemos la siguiente expresión:

val list = join { 
    0 
    1 
    2 
    3 
} 
println(list) 

donde JOIN es una macro cuya aplicación es:

def join(c: Ctx)(a: c.Expr[Int]): c.Expr[List[Int]] = { 
    import c.mirror._ 
    a.tree match { 
    case Block(list, ret) => 
     // c.reify(List(new c.Expr(list(0)).eval, 
     //    new c.Expr(list(1)).eval, 
     //    new c.Expr(list(2)).eval) :+ new c.Expr(ret).eval) 
     c.reify((for (expr <- list) yield new c.Expr(expr).eval) :+ new c.Expr(ret).eval) 
    } 
} 

El objetivo de la macro es unir todos los elementos en el bloque de discusión y de retorno ellos en una sola lista. Como el contenido del bloque puede ser variable, no puedo usar el comentario reificado (que funciona bien). El sin comentario -con una para comprensión, que genera términos libres- arroja el mensaje:

"La expansión de macro contiene la lista de variables de términos libres definida por join en Macros.scala: 48: 18. ¿Ha olvidado usar eval cuando splicing esta variable en un reifee? Si tiene problemas para rastrear variables de término libre, considere el uso de -Xlog-free-terms "

¿Hay alguna manera de introducir el for-comprensión (o un iterador o lo que sea) sin obtener este ¿error? Por cierto, estoy usando 2.10-M3.

Respuesta

15

El problema es que el código mezcla los conceptos de tiempo de compilación y tiempo de ejecución.

La variable "list" que está utilizando es un valor en tiempo de compilación (es decir, se supone que se iterará durante el tiempo de compilación), y está pidiendo reify para retenerlo hasta el tiempo de ejecución (mediante empalme derivado valores). Este enigma de etapas cruzadas conduce a la creación del llamado término libre.

En resumen, los términos libres son los resguardos que se refieren a los valores de las etapas anteriores. Por ejemplo, el siguiente fragmento:

val x = 2 
reify(x) 

se compilará como sigue:

val free$x1 = newFreeTerm("x", staticClass("scala.Int").asTypeConstructor, x); 
Ident(free$x1) 

inteligente, ¿eh? El resultado conserva el hecho de que x es un identificador, conserva su tipo (características de tiempo de compilación), pero, sin embargo, se refiere a su valor también (una característica de tiempo de ejecución). Esto es posible gracias al alcance léxico.

Pero si intenta devolver este árbol desde una expansión de macros (que está incluido en el sitio de llamadas de una macro), las cosas explotarán. El sitio de llamada de la macro probablemente no tenga x en su alcance léxico, por lo que no podría referirse al valor de x.

Lo que es aún peor. Si el fragmento anterior está escrito dentro de una macro, entonces x solo existe durante el tiempo de compilación, es decir, en la JVM que ejecuta el compilador. Pero cuando el compilador termina, ya no está.

Sin embargo, se supone que los resultados de la expansión de macro que contienen una referencia a x se ejecutan en tiempo de ejecución (lo más probable es que en una JVM diferente). Para darle sentido a esto, necesitaría persistencia en varias etapas, es decir, una capacidad para serializar de algún modo valores de tiempo de compilación arbitrarios y deserializarlos durante el tiempo de ejecución. No sé cómo hacer esto en un lenguaje compilado como Scala.


Tenga en cuenta que, en algunos casos, es posible la persistencia en etapas cruzadas.Por ejemplo, si x era un campo de un objeto estático:

object Foo { val x = 2 } 
import Foo._ 
reify(x) 

entonces no sería terminar como un término libre, pero se cosifica de una manera sencilla:

Select(Ident(staticModule("Foo")), newTermName("x")) 

Este es un concepto interesante que también se discutió en la charla de SPJ en Scala Days 2012: http://skillsmatter.com/podcast/scala/haskell-cloud.

Para verificar que alguna expresión no contiene términos libres, en Haskell agregan una nueva primitiva incorporada al compilador, el constructor de tipo Static. Con las macros, podemos hacer esto de forma natural mediante el uso de reify (que en sí mismo es solo una macro). Vea la discusión aquí: https://groups.google.com/forum/#!topic/scala-internals/-42PWNkQJNA.


Bien, ahora hemos visto cuál es exactamente el problema con el código original, entonces, ¿cómo lo hacemos funcionar?

Lamentablemente tendremos que recurrir a la construcción manual de AST, porque reify tiene dificultades para expresar árboles dinámicos. El caso de uso ideal para reify en macrology es tener una plantilla estática con los tipos de agujeros conocidos en el momento de la macro compilación. Haga un paso a un lado, y tendrá que recurrir a la construcción de árboles a mano.

conclusión es que hay que ir con la siguiente (funciona con recientemente publicado 2.10.0-M4, consulte la guía de migración en Scala-idioma para ver qué es exactamente lo que ha cambiado: http://groups.google.com/group/scala-language/browse_thread/thread/bf079865ad42249c):

import scala.reflect.makro.Context 

object Macros { 
    def join_impl(c: Context)(a: c.Expr[Int]): c.Expr[List[Int]] = { 
    import c.universe._ 
    import definitions._ 
    a.tree match { 
     case Block(list, ret) => 
     c.Expr((list :+ ret).foldRight(Ident(NilModule): Tree)((el, acc) => 
      Apply(Select(acc, newTermName("$colon$colon")), List(el)))) 
    } 
    } 

    def join(a: Int): List[Int] = macro join_impl 
} 
+0

¿Habrá algo más fácil en scala 2.11? ¿Las cuasi citas van a ayudar? (No he estudiado macros en profundidad, pero cada vez que lo intento, sigo tocándome con esto) – HRJ

+0

Quasiquotes me ayudarán un poco: https://gist.github.com/densh/6209261 –

Cuestiones relacionadas