lunes, 22 de febrero de 2010

Haciendo un intérprete de LISP (XVIII)

En esta parte de la realización de un intérprete LISP vamos a ver cómo convertirlo en un intérprete de otros lenguajes. Si no de forma completa, sí de manera básica, dando unas pinceladas.

A Lua

El lenguaje Lua es realmente diferente al LISP. La página principal del lenguaje está aquí y existe una interesante entrada en la Wikipedia. A poco que investiguemos el lenguaje nos encontramos con que el tipo de dato fundamental en Lua no es la lista, si no la tabla.

Realmente en LISP ya hemos usado tablas para los entornos. ¿Podremos quitar las listas? La respuesta es sí y muy fácilmente. Basta con realizar las siguientes operaciones:

  1. Permitir que los entornos usen como clave cualquier valor y no únicamente cadenas. A partir de ahora son tablas.
  2. Representar las expresiones mediante tablas. Para esto se puede ver el MetaLua.
  3. Modificar las reglas de evaluación para usar tablas. Ver por ejemplo las metatablas del Lua.

Una lista será ahora una tabla con claves 0, 1, 2, 3... Con este truco, las celdas de tipo CT_PAIR desaparecen de nuestro intérprete. A cambio, abundarán las celdas de tipo CT_TABLE (antiguas celdas de entornos).

Nota: Hay una pequeña diferencia entre un entorno y una tabla. Un entorno puede tener un entorno padre donde se busque una clave no encontrada en él mismo. Generalmente una tabla no tiene estas tablas padre. Nuestras tablas sí las tendrán, ya que las tenemos que usar como entornos también.

Las celdas de tabla mantienen la información en un objeto aparte. Generalmente este objeto será una tabla hash. Las tablas hash son una estructura de datos que no es trivial. Cambian de tamaño, el acceso no es inmediato, hay que mantener la cuenta del factor de carga, etc. Por esta razón habría que implementar una serie de trucos para hacer todo más eficiente. Por ejemplo, el Lua tiene una tabla separada para cuando las claves son números naturales y otra para el resto de accesos.

A Self

Una vez que empezamos a usar tablas surge de forma natural la técnica de guardar en ellas las operaciones que se pueden realizar sobre ellas mismas. De hecho, Lua tiene dos operadores para acceder a tablas. El primero es el punto "." que obtiene el valor bajo una de las claves de la tabla. Por ejemplo, "a.b(c)" busca el valor "b" dentro de la tabla "a" y le aplica los argumentos "(c)".

El otro operador se escribe con dos puntos ":" y es azúcar sintáctico ya que se puede traducir "a:b(c)" por "a.b(a, c)". Es decir, que reutiliza la tabla como primer argumento. Puede verse aquí. Esto significa que usamos la tabla "a" no sólo para buscar la operación "b", sino que también vamos a usar esta tabla como argumento de la operación.

Si siempre nos decantamos por esta segunda opción, pasando la tabla como un argumento implícito (generalmente llamado "self"), lo que tenemos es un lenguaje orientado a objetos puro. En este tipo de lenguajes escribir "a.b(c)" significa "enviar al objeto "a" el mensaje "b" con argumentos (c)". Realmente es igual a lo que escribíamos en Lua "a:b(c)". Esta es la esencia del lenguaje Self.

Para distinguir las tablas a secas de los objetos, en Self se denominan "slots" a las claves de las tablas. Además, existen diversos tipos de slots, aunque no entraremos a discutirlos aquí.

Lo realmente interesante es que Self permite herencia por prototipos. Esto significa que si buscamos una clave en una tabla/objeto y no lo encontramos, hemos de buscar en el objeto prototipo. ¡Pero esto es exactamente lo mismo que buscar en el entorno padre en el caso de los entornos! La única pequeña diferencia de mención es que Self puede tener varios objetos/tablas/entornos padre, ya que permite herencia múltiple.

Otro lenguaje que sigue este esquema de objetos y prototipos es JavaScript.

A Smalltalk

Una vez que empezamos a cambiar lo que ocurre cuando no encontramos una clave/"slot" en un objeto nos lleva a pensar en la siguiente simplificación: ¿para qué buscar en un objeto y, si no está, vamos a otro? ¡Directamente buscamos en el otro! Esta es la idea del uso de clases.

Una clase es un objeto que nos dice qué "slots" tiene otro objeto. Se consideran entonces a las clases como "patrones" de los objetos relacionados con ella. Este sistema tiene una gran ventaja: no se repite información. Sin embargo, esta mejora se consigue a cambio de aumentar la complejidad.

Smalltalk es un lenguaje que hace esta distinción entre objeto y clase. Además, es un lenguaje con muchas sorpresas tanto sintácticas como semánticas. Pero este artículo se está haciendo demasiado largo y le dejo al lector el gozo de investigarlas.


0 comentarios:

Publicar un comentario