miércoles, 16 de diciembre de 2009

Haciendo un intérprete de LISP (VIII)

En este mensaje vamos a ver cómo funciona la aplicación de una función lambda.

Creación del entorno local

El primer paso en la aplicación de una función lambda es crear un entorno local. Esto es necesario para dos cosas. La primera es aislar la evaluación de la función del entorno de uso de la función. Si lo lo aisláramos, se podría definir un símbolo en el entorno de uso. Lo que es antihigiénico. También usamos el entorno local para vincular el resultado de la evaluación de los argumentos a los nombres de los parámetros.

Cell& Lambda::Apply(Heap& heap, Cell& args, Cell& envir)const
{
Cell& local_cell=heap.CreateEnvironment(&m_Closure);
Environment& local=local_cell.GetEnvironment();

La creación de un entorno ha de estar sujeta a la gestión de memoria. Por esa razón lo creamos en una celda y, dentro de la celda, tenemos el objeto Environment que es realmente el entorno. Para simplificar futuros uso en la aplicación, le damos el nombre "local" al propio entorno.

Vinculación de los resultados de los argumentos

Ahora debemos ir argumento por argumento evaluándolos en el entorno de uso. El resultado de cada evaluación debe ser vinculada a cada parámetro. Así pues, necesitamos dos listas: la de los argumentos y la de los parámetros.

 Cell const *a, *p;

Las iremos recorriendo a la vez. Desde el principio hasta que una de las dos no sea un CT_PAIR. En ese momento terminaremos. Si no hemos terminado, avanzamos en ambas a la vez.

 for(a=&args, p=&m_Params; a->IsPair() && p->IsPair(); a=&a->GetTail(), p=&p->GetTail())

El interior del bucle debe primero evaluar el argumento y luego vincularlo. Es importante recordar aquí que estamos apuntando a una lista y para obtener el elemento que hay en cabeza de la lista hay que ir a su cabeza.

 local.Bind(heap, *p->GetHead().GetName(), heap.Evaluate(a->GetHead(),envir));

Los parámetros han de ser nombres así que si no lo son GetName() fallará con una excepción. La evaluación es en "envir". Este es el entorno de uso desde el que se llama a la función. El método "Bind" queda por ser definido pero lo que hace es relacionar el nombre pasado con el valor pasado (celda pasada).

Una comprobación de aridad

En este punto puede ocurrir que haya más argumentos que parámetros o más parámetros que argumentos. En ambos casos es un error y debemos lanzarlo.


if((a->IsEmpty())!=(p->IsEmpty()))
throw std::runtime_error("Incorrect arity");

Como vemos, es fácil de detectar ya que basta con que una de las dos listas no esté vacía. Como sabemos que una de las dos no es un CT_PAIR y que ambas son listas, la comprobación se simplifica a lo puesto arriba.

La evaluación del cuerpo

Ya tenemos el entorno local con los parámetros vinculados a los valores de los argumentos correspondientes. Eso significa que si evaluamos ahora el cuerpo y nos encontramos con un nombre de parámetro, al estar vinculado al valor del argumento, se evaluará a dicho valor.


return heap.Evaluate(m_Body, local_cell);
}

Ni que decir tiene que al evaluar dentro de una aplicación abrimos la puerta a la recursividad. Esto es deseable.

Esto es el núcleo de cualquier LISP. En los siguientes posts veremos algunos detalles menores: la implementación de los entornos (y el método Bind), evaluación de secuencias y poco más.

0 comentarios:

Publicar un comentario