scala - Empalme de un cuerpo de función pasada en una expresión macro-reescrita
macros expression (2)
Estaba jugando con las nuevas características macro de Scala 2.11. Quería ver si podía hacer la siguiente reescritura:
forRange(0 to 10) { i => println(i) }
// into
val iter = (0 to 10).iterator
while (iter.hasNext) {
val i = iter.next
println(i)
}
Creo que me acerqué bastante a esta macro:
def _forRange[A](c: BlackboxContext)(range: c.Expr[Range])(func: c.Expr[Int => A]): c.Expr[Unit] = {
import c.universe._
val tree = func.tree match {
case q"($i: $t) => $body" => q"""
val iter = ${range}.iterator
while (iter.hasNext) {
val $i = iter.next
$body
}
"""
case _ => q""
}
c.Expr(tree)
}
Esto produce la siguiente salida cuando se llama forRange(0 to 10) { i => println(i) }
(al menos, es lo que la función show
me proporciona en el árbol resultante) :
{
val iter = scala.this.Predef.intWrapper(0).to(10).iterator;
while$1(){
if (iter.hasNext)
{
{
val i = iter.next;
scala.this.Predef.println(i)
};
while$1()
}
else
()
}
}
Parece que debería funcionar , pero hay un conflicto entre mi val i
definido manualmente y el que se hace referencia en el cuerpo de la función empalmado. Obtuve el siguiente error:
ReplGlobal.abort: el valor del símbolo i no existe en $ line38. $ Read $$ iw $$ iw $$ iw $$ iw $$ iw $$ iw $$ iw $$ iw. error: el valor del símbolo i no existe en scala.reflect.internal.FatalError: el valor del símbolo i no existe en $ line38. $ read $$ iw $$ iw $$ iw $$ iw $$ iw $$ iw $$ iw $$ iw.
Y luego una traza de pila bastante grande, lo que da como resultado una notificación de "sesión bloqueada abandonada".
No puedo decir si esto es un problema con mi lógica (simplemente no puede empalmar en un cuerpo de función que hace referencia a una variable cerrada), o si se trata de un error con la nueva implementación. El informe de errores ciertamente podría ser mejor. Puede ser exacerbado por el hecho de que estoy ejecutando esto en la Repl.
¿Es posible separar una función, separando el cuerpo de los términos cerrados, y reescribirlo para empalmar la lógica directamente en un árbol resultante?
En caso de duda, resetAllAttrs
:
import scala.language.experimental.macros
import scala.reflect.macros.BlackboxContext
def _forRange[A](c: BlackboxContext)(range: c.Expr[Range])(
func: c.Expr[Int => A]
): c.Expr[Unit] = {
import c.universe._
val tree = func.tree match {
case q"($i: $t) => $body" => q"""
val iter = ${range}.iterator
while (iter.hasNext) {
val $i = iter.next
${c.resetAllAttrs(body)} // The only line I''ve changed.
}
"""
case _ => q""
}
c.Expr(tree)
}
Y entonces:
scala> def forRange[A](range: Range)(func: Int => A) = macro _forRange[A]
defined term macro forRange: [A](range: Range)(func: Int => A)Unit
scala> forRange(0 to 10) { i => println(i) }
0
1
2
3
4
5
6
7
8
9
10
En general, cuando agarras un árbol de un lugar y lo colocas en otro lugar, es probable que sea necesario utilizar resetAllAttrs
para obtener todos los símbolos correctos.
Oscar Boykin señaló en Twitter que mi respuesta anterior ya no funciona, y de todos modos no fue una respuesta muy completa: aborda el problema señalado por el OP en Scala 2.10, pero no tiene cuidado con la higiene, si usted escribió iter => println(iter)
obtendría una falla en tiempo de compilación, por ejemplo.
Una mejor implementación para 2.11 usaría un Transformer
para reescribir el árbol después de eliminarlo.
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
def _forRange[A](c: Context)(r: c.Expr[Range])(f: c.Expr[Int => A]): c.Tree = {
import c.universe._
f.tree match {
case q"($i: $_) => $body" =>
val newName = TermName(c.freshName())
val transformer = new Transformer {
override def transform(tree: Tree): Tree = tree match {
case Ident(`i`) => Ident(newName)
case other => super.transform(other)
}
}
q"""
val iter = ${r.tree}.iterator
while (iter.hasNext) {
val $newName = iter.next
${ transformer.transform(c.untypecheck(body)) }
}
"""
}
}
def forRange[A](r: Range)(f: Int => A): Unit = macro _forRange[A]
Que funciona así:
scala> forRange(0 to 10)((i: Int) => println(i))
0
1
2
3
4
5
6
7
8
9
10
Ahora no importa qué nombre de variable usemos en nuestra función literal, ya que simplemente se reemplazará con una variable nueva de todos modos.