sábado, febrero 10, 2007

Iteración en Freya

¡Ya funcionan los iteradores en Freya! Había postergado la "conexión" del módulo correspondiente, pensando que iba a resultar más complicado, pero al final ha sido muy sencillo.
iterator Stack: X;
begin
for var
i := Count - 1 downto 0 do
yield
Items[i];
end;
El truco principal consiste en recorrer el cuerpo del método iterador buscando parámetros y variables locales. Para cada uno de ellos, se crea un campo para la clase que genera el compilador internamente, y las referencias a variables y parámetros se sustituyen por referencias a los campos correspondientes. Hay que tener cuidado, porque las referencias a campos necesitan empujar antes dentro de la pila la referencia a la instancia de la clase. Un detalle elegante: las referencias a campos de la clase donde se define el iterador son muy fáciles de traducir, porque la referencia a Self se considera también un parámetro del iterador, y como tal, se duplica en la clase enumerable generada por el compilador.
Por supuesto, quedan algunos pequeños detalles por resolver. Por ejemplo, hay que tener cuidado con los iteradores estáticos, como los que se definirían en una sección de implementación anónima, y hay que mejorar los mensajes de error del compilador cuando se escribe una instrucción yield en zonas prohibidas, como la sección finally de un try/finally.
¿Qué tal un ejemplo más complejo? Este es un iterador para recorrer en preorden los nodos de un árbol binario:
iterator PreOrder: Y;
var
St: Stack[TreeNode];
begin
if Root <> nil then
begin

St := new Stack[TreeNode];
St.Push(Root);
repeat
var t := St.Pop;
if t.Left <> nil then
St.Push(t.Left);
if t.Right <> nil then
St.Push(t.Right);
yield t.Value;
until St.Count = 0;
end;
end;
Es interesante ver cómo se traduce el código anterior, según Reflector. El compilador crea una clase privada y anidada dentro del árbol, que implementa la interfaz IEnumerable. Así es como Reflector traduce a C# el código IL generado por el compilador para el método MoveNext:
public sealed override bool MoveNext()
{
switch (this.state$)
{
case 0:
if (this.Self.Root == null)
goto Label_00D1;
this.St = new Stack<BinaryTree<Y>
.TreeNode>();
this.St.Push(this.Self.Root);
break;
case 1:
this.state$ = -1;
if (this.St.Count != 0)
break;
goto Label_00D1;
default:
goto Label_00D1;
}
this.t = this.St.Pop();
if (this.t.Left != null)
this.St.Push(this.t.Left);
if (this.t.Right != null)
this.St.Push(this.t.Right);
this.current$ = this.t.Value;
this.state$ = 1;
return true;
Label_00D1:
this.state$ = -2;
return false;
}
Parece más complejo de lo que realmente es... cuando se intenta traducir del formato IL a un lenguaje estructurado.

Etiquetas: