Casting SPELs

Capítulo 7:
Casting SPELs.

Vamos a conocer en este capítulo un elemento del lenguaje increíblemente potente. Conrad Barski lo llama SPEL (Semantic Program Enhancement Logic). Es más conocido con el nombre de macro, pero tiene muy poco en común con las macros de C++. En realidad, tal como indica el nombre SPEL, la cosa va de magia (Nota: se trata de un juego de palabras: el término inglés spell significa encantamiento). Las SPEls nos van a permitir adaptar el lenguaje de Maxima en su nivel más básico a nuestros objetivos. Nos convertiremos en diseñadores de un lenguaje.

La función de Maxima que permite hacer macros es buildq, de build quoted. Este nombre nos va a permitir ver de una forma muy hermosa de qué va una macro.

En Maxima, el término quote se asocia a expresiones nominales, esto es, se trata de que un apóstrofo precediendo a un símbolo indica que éste no se evalúa. El hecho de poder escribir expresiones que pueden tomar valores en forma diferida abre muchas posibilidades que, como queda dicho, rayarán con la magia. La definición de una macro se hace escribiendo ::= en lugar de := .

En honor a Conrad Barski, a la función buildq deberíamos llamarla SPEL,

SPEL([rest])::= buildq([rest],buildq(splice(rest)))$

Esta redefinición muestra de una forma refinida, pero también abstracta, cómo funciona buildq. La función buildq necesita dos argumentos: una lista con variables de sustitución y una expresión. Si como es habitual la definición anterior se evaluase de dentro hacia fuera hubiésemos recibido de forma inmediata un mensaje de error; el analizador sintáctico se habría percatado de que al buildq más interno sólo tiene un argumento. Sin embargo, lo que se hace es introducir los valores de las variables de sustitución en la expresión sin evaluar ésta.

De izquierda a derecha: la expresión [rest] en la definición de una función significa que ésta tiene argumentos opcionales, los cuales serán guardados en una lista de nombre rest. La función buildq coloca entonces esta lista en la expresión buildq(splice(rest)). La función splice juega un papel importante en esta sustitución, convirtiendo nuevamente la lista rest en argumentos independientes. Cuando la función SPEL es invocada exactamente con dos argumentos, entonces al final buildq tendrá también estos mismos dos argumentos. De esta manera podemos utilizar el símbolo SPEL en lugar de buildq.

Primero sustituimos y luego evaluamos. Este truco permite escribir funciones que construyan nuevas funciones, u otro tipo de objetos, durante la ejecución de un programa.

En la siguiente definición de la macro game_action vamos a ver de forma más concreta cómo funciona SPEL.

game_action(command,subj,obj,place,[rest])::= SPEL( 
   [command,subj,obj,place,rest], 
   block( 
      infix(command), 
      command(subject,object):= block(
         if location = place 
            and subject = subj
            and object = obj 
            and have(subj) then apply(sconcat,rest)
         else sconcat("you cannot ",command," like that. ") )))$

Si nos fijamos con atención, vemos que efectivamente SPEL sólo tiene dos argumentos: la lista con las variables de sustitución y un bloque, el cual contiene exactamente el código que antes tenían en común las definiciones de las funciones weld y dunk. Es una estructura. En principio, game_action es una especie de patrón, dentro del cual se introducen los nombres de las instrucciones, sujetos, objetos y lugares en los que debe desarrollarse la acción. Los diferentes textos de los comandos se pasan entonces como parámetros opcionales.

Un ejemplo vale más que mil palabras. Redefinimos ahora con la ayuda de game_action la instrucción weld. Si al final de este código cerramos con un punto y coma, obtenemos la respuesta de esta macro, que nos es ya conocida. En este caso es la definición del operador weld.

game_action("weld",chain,bucket,attic,
   if have(bucket)      
      and not chain_welded then (
         chain_welded: true,
         "the chain is now securely welded to the bucket. " )
   else "you do not have a bucket. ");

==> subject weld object := block(
if location = attic 
   and subject = chain
   and object = bucket 
   and have(chain) then 
      apply(sconcat, 
            [if have(bucket) 
                and not chain_welded then (
                   chain_welded : true, 
                   "the chain is now securely welded to the bucket. ")
             else "you do not have a bucket. "]) 
else sconcat("you cannot ", "weld"," like that. "))

¡Magia!, ¡SPEL!

Si el compilador de Lisp, que en nuestro juego es la rana del mago, acaba realmente sudando haciendo esta tarea, es algo que jamás sabremos.

La macro game_action permite que durante la definición de los comandos nos podamos concentrar únicamente en sus características distintivas. En conjunto el código toma una forma más bonito y clara. En el caso de dunk tenemos

game_action("dunk",bucket,well,garden,
   if chain_welded then (
      bucket_filled: true,
      "the bucket is now full of water. " )
   else "the water level is too low to reach. ")$

Y ahora nuestra última instrucción: splash.

Si hay agua en el balde, se la echamos por encima al mago.

game_action("splash",bucket,wizard,living_room,
   if not bucket_filled then "the bucket has nothing in it. "
   else if have(frog) then sconcat(
      "the wizard awakens and sees that you stole his frog. ",
      "he is so upset he banishes you to the netherworlds ",
      "- you lose! the end. ")
   else sconcat(
      "the wizard awakens from his slumber and greets you warmly. ",
      "he hands you the magic low_carb donut - you win! the end. ") )$

Con esto has escrito un auténtico juego de aventuras.

Ahora deberías jugar a este juego... y con un poco de suerte el mago te dará tu recompensa.

¡Un magic low_carb donut!
Introducción | Capítulo 1 | Capítulo 2 | Capítulo 3 | Capítulo 4 | Capítulo 5 | Capítulo 6 | Capítulo 7 | Agradecimientos | Licencia

© Mario Rodríguez Riotorto, 2006