domingo, 17 de enero de 2010

Haciendo un intérprete de LISP (XIII)

En la última entrada introducimos SetQ. Este SetQ es distinto a los del LISP usual en dos aspectos. El primero está relacionado con la capacidad que tiene el SetQ usual del LISP para modificar varios símbolos a la vez. El segundo, sin embargo, sí que es importante ya que está relacionado con la clausura léxica.

Inspeccionando el estado del intérprete

Vamos a introducir unas funciones nativas para inspeccionar el montículo y los entornos. Usualmente esto se hace en LISP con órdenes (commands). De hecho, la salida del intérprete con Quit también suele ser una orden pero, para mantenerlo simple todo, usaremos funciones nativas.

Para inspeccionar el montículo usaremos esta función nativa:

Cell& FOL_Heap(Heap& heap, Cell& args, Cell& envir)
{
if(!args.IsEmpty())
throw std::runtime_error("Heap needs no arguments");

heap.Print(std::wcout);

return args;
}

Para inspeccionar los entornos usaremos esta función nativa:

Cell& FOL_Envir(Heap& heap, Cell& args, Cell& envir)
{
INTEGER depth=args.IsEmpty() ? 0 : heap.Evaluate(args.GetHead(), envir).GetInteger();
Cell* e=&envir;
while(depth>0)
{
--depth;
Cell* parent=e->GetEnvironment().GetParent();
if(parent!=NULL)
e=parent;
}

return *e;
}

Esta última función toma un argumento opcional que indica qué nivel de entorno padre queremos inspeccionar.

Inspeccionando entornos padre

Lo veremos con un ejemplo. Definimos esta función:

> (setq f (lambda (x) (envir x)))

<lambda 003A9AD0 <envir 003A6828> (x)
((envir
x))>

Luego, la usamos:

> (f 0)

<envir 003A9438>= <envir
(x
0)
>

El entorno donde se evaluó (envir 0) era el que tenía definido únicamente la x. El resto de símbolos está definido en el entorno padre.

> (f 1)

<envir 003A6828>= <envir
(add
<native 003A6930>)
(envir
<native 003A6A48>)
...

Omito el resto de símbolos. Como ya hemos visto, cuando un símbolo no se encuentra en un entorno, se recurre al entorno padre.

El problema de setq

El problema de setq radica en que, tal como está, sólo modifica el entorno local. Incluso cuando el símbolo a modificar esté en un entorno padre. En el caso de las funciones este entorno padre es la clausura léxica.

> (setq f (lambda (x) (setq y 3) (envir x)))

<lambda 003A74A0 <envir 003A6828> (x)
((setq
y
3)
(envir
x))>

> (f 0)

<envir 003AA810>= <envir
(x
0)
(y
3)
>

Sin embargo, si predefino el símbolo "y", no obtengo el resultado deseado.

> (setq y 5)

5

> (f 0)

<envir 003AA810>= <envir
(x
0)
(y
3)
>

> y

5

Hay que modificar el comportamiento de setq para que actúe no sólo en el entorno local. También debe modificar los entornos padre si el símbolo a modificar está allí.

Cambiando el vínculo

Introducimos un nuevo método en los entornos. Buscaremos en los entornos padre y modificaremos el símbolo en el entorno donde lo encontremos. Si no lo encontramos, lo definimos localmente.

Cell& Environment::ChangeBond(Heap& heap, STRING name, Cell& cell)
{
//Modifiy if already defined.
Environment* current=this;
while(current!=NULL)
{
BONDMAP::iterator i=current->m_Bonds.find(name);
if(i!=current->m_Bonds.end() && i->second!=NULL)
{
i->second=&cell;
return cell;
}

//Go to parent's (if it exists)
if(current->m_Parent==NULL)
break;

current=&current->m_Parent->GetEnvironment();
}

//Define if not defined.
m_Bonds[name]=&cell;
return cell;
}

El resultado es que ahora podemos hacer lo que queríamos hacer: modificar símbolos del entorno de definición.

> (setq f (lambda (x) (setq y 3) (envir x)))

<lambda> (x)
((setq
y
3)
(envir
x))>

> (setq y 5)

5

> (f 0)

<envir 003AA810>= <envir
(x
0)
>

> y

3

>


0 comentarios:

Publicar un comentario en la entrada