Aprovechando la potencia de GNU Common Lisp (GCL)
Quien más quien menos ha oído hablar alguna vez de Lisp y del paradigma de la programación funcional. Lisp es un lenguaje de programación que no suele encontrarse fuera del mundo universitario y normalmente suele estar asociado a la Inteligencia Artificial (IA), aunque en realidad Lisp puede usarse para cualquier cosa e incluso puedo decir que hay problemas de complicada resolución con otros lenguajes de programación que en Lisp pueden ser bastante triviales. Aún así los programadores de Lisp no abundan en el mundo, y aquí en Castellón estoy bastante convencido de que me sobra una mano para contarlos.
No voy a hablar sobre los pros y los contras de programar en Lisp, pero si te gusta Lisp o te ha picado el gusanillo con mi breve introducción o quieres conocer algo más que lenguajes de programación imperativa, te recomiendo que leas alguno de los ensayos sobre Lisp de Paul Graham o alguna de las entradas de Slava Akhmechet en defmacro.
GNU Common Lisp, de ahora en adelante GCL, es la implementación oficial de Lisp usada en el proyecto GNU. Existen otras implementaciones libres de Lisp que tienen más utilidades que GCL, como librerías de sockets, multithreading, etc, pero el hecho de que GCL fuera la preferida por los de GNU me animó a jugar un poco con ella y he de reconocer que no me siento decepcionado.
Al contrario que con los compiladores de otros lenguajes, que compilan los programas a bytecode para despues ejecutarlos con una máquina virtual o compilarlos a lenguaje máquina, GCL compila a C para realizar una segunda compilación mediante gcc o el compilador de C que tengamos en nuestro sistema. Gracias a esta integración entre Lisp y C es posible programar con GCL usando C para bajo nivel (acceso a registros, memória, punteros, etc) y Lisp para alto nivel (gestión de listas, árboles, operaciones con funciones, macros, etc). Resumiendo, tenemos toda la potencia de C, lo que significa que podemos crear nuestras propias funciones de multithreading por ejemplo, y la potencia de un lenguaje de alto nivel como Lisp.
Para lograrlo necesitamos solamente tres macros de GCL: clines, defentry y defcfun. Eso e instalar GCL, claro.
Probaremos a hacer una prueba sencilla en la que definiremos una función en C que sume dos enteros y devuelva el resultado y usaremos esa función desde Lisp. Primero crearemos el fichero "misuma.lsp" con estas dos lneas:
(clines "int misuma (int x, int y) { return x + y; }")
(defentry mi-suma (int int) (int "misuma"))
Con clines podemos introducir cadenas de texto directamente en el código C resultado de la compilación del programa. En este caso declaramos la función misuma. Y con defentry declaramos la función mi-suma en Lisp, que recibe dos enteros como parámetros y devuelve otro entero desde la función C misuma.
Ahora ya podemos entrar en el intérprete de Lisp y compilar nuestro pequeño programa. Cuando se programa en Lisp no es necesario compilar ya que es un lenguaje interpretado (se suele compilar cuando el programa ya está acabado para ganar velocidad) pero debemos hacerlo si queremos integrar Lisp y C.
Compilamos con (compile-file "misuma.lsp")
y cargamos el código objeto que hemos generado con (load "misuma.o")
. Ahora ya tenemos nuestra nueva función C disponible en Lisp. Si ejecutamos (mi-suma 3 2)
obtendremos 5. ¿Impresionante, verdad? ¿No? Bueno, puede que no lo parezca pero esto es la punta del iceberg, una punta muy pequeña, ya que lo mismo que hemos hecho con la suma podemos hacerlo con la librería matemática de C, o con las librerías GTK, o con Qt, etc.
Estoy realizando un proyecto en el que he integrado SDL con Lisp consiguiendo resultados bastante espectaculares, pero de eso hablaremos otro día.
Me he dejado lo mejor para el final. ¿Es posible hacer una función en la que se mezclen instrucciones de C con instrucciones de Lisp? Pues podemos hacerlo gracias a defcfun. Vamos a verlo.
Volvemos a editar "misuma.lsp" y añadimos las siguientes líneas:
(defcfun "static object otrasuma (int x, int y)" 0
"object z;"
"int s = x + y;"
('+ z)
((list z (int x) (int y)) z)
((cons z (int s)) z)
"return z;")
(defentry otra-suma (int int) (object "otrasuma"))
Si volvemos a compilar y ejecutamos (otra-suma 10 7)
obtendremos la lista ((+ 10 7) . 17)
. Veamos cómo lo hace.
En primer lugar indicamos la cabecera de la función con su nombre y argumentos. El código en C se escribe usando cadenas de texto. Declaramos una variable z que contendrá la lista resultado y un entero s con el resultado de la suma.
La macro defcfun usa una sintaxis especial de Lisp que no voy a explicar (que para eso está la documentación) de forma que ('+ z)
es lo mismo que hacer (setq z '+)
desde el intérprete. Ahora ya tenemos que z contiene (+).
Con la siguiente instrucción creamos la lista que representa la operación, con lo que z pasa a contener (+ 10 7). Y finalmente hacemos el cons con el resultado de la suma que teníamos en s, con lo que nos queda la lista que hemos obtenido al ejecutar la función.
¿Y si ejecutamos (= (eval (car (otra-suma 10 7))) (cdr (otra-suma 10 7)))
? Por la salud mental de todos espero que nos devuelva T ;-)