Notxor tiene un blog

Defenestrando la vida

lisp, slime y emacs

2020-12-23

Este es otro artículo que intenta responder preguntas que me hace la gente. Concretamente hay alguien que me ha preguntado cómo tengo configurado mi entorno de trabajo para trabajar con lisp. Dicho así en genérico sin especificar ninguno de sus sabores. Por las dudas que me planteado, tampoco tiene muy claro si tirar por lisp o seguir por scheme, y para eso yo no tengo respuesta, porque depende de los gustos de cada quien. Él ya ha estado mirando algo de scheme, aunque se declara novato, pero le interesa también saber algo más sobre lisp para comparar y decidir. Intentaré contestar lo mejor que pueda.

Lisp

Lisp no es un único lenguaje o compilador. Ya hay toda una familia de lenguajes como son scheme, clojure, lisp... Además de estos grandes grupos con distintas características, cada uno tiene distintos compiladores o sistemas. Entre los scheme podemos encontrar guile o chicken, por ejemplo, entre los lisp tienes para elegir varios common-lisp, emacs-lisp y otros. Además, entre los common-lisp hay varios sistemas distintos. Puedes leer sus licencias y/o sus características y elegir el que más rabia te dé. Pero como también me preguntas cuál uso yo, pues te lo simplifico: yo utilizo SBCL1.

Las diferencias fundamentales entre scheme y lisp son que scheme sólo tiene una tabla de asignación, y por tanto no puedes llamar de la misma manera a una variable y a una función, y que es case sensitive mientras que lisp no diferencia entre mayúsculas y minúsculas. En cambio, tiene dos tablas de asignación y por tanto, tienes que emplear las instrucciones defvar y defun, para definir variables y funciones. Pero ambos son muy similares y el problema de los paréntesis lo tienes en ambos, pero sólo hasta que te acostumbras a esa sintaxis, con el tiempo casi ni te fijas en ellos.

En mi caso, la instalación de sbcl no tiene ningún misterio. En OpenSuse hay un paquete con dicho nombre y se instala como cualquier paquete de la distro. Según qué sistema o distro uses, seguro que hay algún paquete que te facilite la vida. En mi caso:

sudo zypper install sbcl

En el caso de las derivadas de Debian supongo que la instrucción será alto estilo apt-get install sbcl. Comprueba el nombre del paquete en cuestión.

Libros

Para la documentación que pides te recomiendo dos libros que puedes encontrar por Internet:

Además, si instalas sbcl, al menos en OpenSuse, viene con un manual info que se integra perfectamente con el sistema de ayuda de Emacs, así que allí puedes encontrar también más información sobre cómo funciona sbcl de la que puedo dar yo en un pequeño artículo.

Librerías de quicklisp

Si ya estás trabajando con Emacs, ya conocerás su sistema de paquetes y lo tendrás configurado. Para lisp existe algo similar que se llama quicklisp. Si ya tienes instalado sbcl en el sistema, lo único que tienes que hacer es lo siguiente:

curl -O https://beta.quicklisp.org/quicklisp.lisp
sbcl --load quicklisp.lisp --eval '(quicklisp-quickstart:install)' --quit
sbcl --load ~/quicklisp/setup.lisp --eval '(ql:add-to-init-file)' --quit

La primera línea obtiene el código de quicklisp.lisp de su sitio de Internet. La segunda línea obtiene los paquetes e información que necesita y lo guarda en el directorio ~/quicklisp. Por último, la tercera línea ejecuta el código de configuración para añadirse al fichero ~/.sbclrc.

Esto es totalmente optativo, pero si vas a desarrollar con lisp te puede facilitar un poco la vida, ya decides tú si quieres instalarlo o no. Como digo para empezar no es necesario, pero más adelante es posible que lo eches en falta.

Slime y otros paquetes de Emacs

SLIME2 es una herramienta de Emacs que facilita la vida a los programadores de lisp. Instalarlo es tan fácil como cualquier otro paquete de nuestro editor favorito:

package-install RET slime

Una vez que lo tenemos instalado en nuestro Emacs, tenemos que configurarlo. Es algo sencillo, pues sólo necesita que añadamos una línea en nuestro init.el que le informe qué lisp tiene que correr:

(setq inferior-lisp-program "/usr/bin/sbcl")

Comprueba que en tu caso el path coincide antes de hacer el corta-pega.

Si lo hemos instalado y configurado, podremos utilizarlo directamente si cargamos de nuevo el fichero de configuración con load-file o bien tras reiniciar Emacs. El paquete proporciona un entorno interactivo de lisp y funciones que cargan a dicho entrono con el código que vayamos escribiendo. Nos permite trazar y depurar código, etc.

El paquete paredit

Dices que, en scheme, te atascas con tanto paréntesis y pierdes la cuenta de los que abres y los que debes cerrar. Lo siento, pero en lisp no notarás demasiada mejora, pero no desesperes en ninguno de los dos casos. La solución más rápida y relajada es que actives show-paren-mode, bien en el apartado custom de tu init.el o que en cualquier sitio de ese fichero de configuración tengas algo como:

(setq show-paren-mode t)

Esa opción lo que hace es mostrar el paréntesis pareja del paréntesis sobre el que está situado el cursor. Bueno, no sobre el que está situado, eso es así cuando te sitúas en el de apertura. Para ver el que corresponde a uno de cierre tienes que situarte justo un carácter a la derecha... pero vamos, esto es más fácil de entender viéndolo que de explicarlo así con palabras: activa el modo y observa cómo se comporta. Si no quieres tenerlo activado de manera constante, también lo puedes activar con algún hook para los modos que quieras, o llamando a show-paren-mode cuando lo quieras activar.

Por otro lado, hay otros paquetes que te pueden echar una mano. Por ejemplo, también uso el paquete paredit. Lo que hace este paquete es que te escribe directamente la pareja de cierre de todo paréntesis o corchete que abras. Hay a quien le puede molestar este comportamiento, pero a lo mejor te facilita la vida. En todo caso, es recomendable activarlo sólo cuando programes, principalmente con lisp, scheme o lenguajes similares, aunque nada te impide usarlo siempre. Lo puedes activar mediante hooks:

(add-hook 'emacs-lisp-mode-hook 'enable-paredit-mode)
(add-hook 'lisp-mode-hook 'enable-paredit-mode)

Recuerda poner el hook para cada uno de los modos, porque la de lisp-mode-hook no incluye a emacs-lisp-mode-hook. Son listas y modos independientes y, por tanto, tienes que activar el modo en los dos. Otra opción sería que se activara en prog-mode-hook y así se cargaría en todos los modos de programación, que es otra opción aceptable.

Echa un ojo a la lista de paquetes que hay disponibles porque hay muchos que sirven para destacar los pares de paréntesis. Algunos los colorean, otros los hacen parpadear... en fin, hay muchos paquetes que te pueden ser útiles, elige el que más te guste.

Usando slime

Bien, ya tienes todo instalado y tienes ganas de probarlo escribiendo un poco de código. Pues vamos a ello... prometo ser original con los ejemplos:

  1. Abre Emacs y crea un buffer para los ejemplos:

    C-x C-f ejemplos.lisp
    
  2. Escribe el código del primer ejemplo3:

    (defun hola-mundo ()
      "Escribe un mensaje de «¡Hola Mundo!»"
      (format t "¡Hola mundo!"))
    
  3. Lanza slime con el comando:

    M-x slime
    

    Si tenemos todo bien configurado, debería aparecer un buffer con nombre *slime-repl sbcl* con un mensaje de prompt tal que CL-USER>. Si intentas ejecutar ahí (hola-mundo) te lanzará un error. Vuelve al buffer donde está el código, no te precipites. Primero tienes que cargarle el código que has escrito.

  4. Evaluamos el código: slime tiene varias funciones de evaluación de código, en nuestro caso vamos utilizar M-x slime-eval-buffer, pero también se puede hacer para una función o para una región de texto seleccionado.
  5. Vuelve al buffer de slime y prueba a ejecutar (hola-mundo)

    CL-USER> (hola-mundo)
    ¡Hola mundo!
    NIL
    CL-USER> _
    

Bien ya tienes el sistema funcionando... Ahora a escribir código.

Bueno, vale, lo mismo quieres algún ejemplo más antes de lanzarte tú solo a la aventura. Prueba con el siguiente:

(defun factorial (n)
  "Calcula el factorial del número n."
  (if (= n 0)
      1
      (* n (factorial (- n 1)))))

Vale, ya sé que no estoy siendo muy original con los ejemplos, pero tampoco te pongas muy estupendo, que sólo intento explicarte las herramientas, la programación ya la tienes que poner tú. Bien, al caso. Si has evaluado esta función cargando el buffer completo con slime-eval-buffer, como hicimos antes, es posible (seguro) que te diga que ya existía una función hola-mundo y te lance un warning. En ese caso, lo puedes ignorar, porque estamos trabajando en el mismo código. Si aún no lo has cargado y quieres evitar el warning sitúa el cursor en cualquier línea de la definición de la función y utiliza el comando M-x slime-eval-defun. Como ves, era sólo por mostrar otra función que carga código. A partir de ese momento, ya estará la función factorial también disponible en slime:

CL-USER> (factorial 6)
720
CL-USER> _

Por darle otra vuelta, prueba a hacer un trazado de la función factorial, por ejemplo:

CL-USER> (trace factorial)
(FACTORIAL)
CL-USER> (factorial 5)
  0: (FACTORIAL 5)
    1: (FACTORIAL 4)
      2: (FACTORIAL 3)
        3: (FACTORIAL 2)
          4: (FACTORIAL 1)
            5: (FACTORIAL 0)
            5: FACTORIAL returned 1
          4: FACTORIAL returned 1
        3: FACTORIAL returned 2
      2: FACTORIAL returned 6
    1: FACTORIAL returned 24
  0: FACTORIAL returned 120
120
CL-USER>

Queda bonito ver cómo se desarrolla ─y desenrrolla─ la llamada a una función recursiva y lo que va devolviendo.

Paquetes externos

Si antes de llegar hasta aquí hiciste la instalación de quicklisp y quieres probar cómo va esto de los paquetes externos. Vamos con un ejemplo básico también para que puedas comprobar cómo funcionan. Por ejemplo, hunchentoot es un servidor web hecho enteramente con lisp y queremos ponerlo en marcha en nuestro sistema. Vamos por pasos:

  1. Crea un buffer nuevo con C-x C-f servidor.lisp
  2. En ese buffer teclea el siguiente código:

    (ql:quickload "hunchentoot")
    (hunchentoot:start (make-instance 'hunchentoot:easy-acceptor :port 6969))
    
  3. Evalúa el código anterior con slime. Después de unos segundos bajando y preparando paquetes aparecerá el mensaje de que el servidor está funcionando:

    To load "hunchentoot":
      Load 1 ASDF system:
        hunchentoot
    ; Loading "hunchentoot"
    .....
    

Si apuntamos cualquier navegador a la dirección localhost:6969 obtendremos una ventana como la siguiente:

Captura-pantalla_servidor-lisp.png

Y ya vemos cómo de fácil es obtener todo un servidor

Conclusiones

Estas herramientas están hechas para funcionar conjuntamente, puedes ponerle más cosas: configurarle el modo lsp, o lo que más rabia te dé, pero esto es lo básico, básico, y un poco más que aporta quicklisp.

Si has utilizado Emacs para trastear con scheme verás que slime es a lisp lo que geiser es a scheme. No se trabaja de forma idéntica en ambos, pero sí muy similar.

Nota al pie de página:

1

Steel Bank Common Lisp http://www.sbcl.org

2

Superior Lisp Interaction Mode for Emacs

3

No esperabas tanta originalidad, seguro.

Categoría: lisp emacs slime

Comentarios

Debido a algunos ataques mailintencionados a través de la herramienta de comentarios, he decidido no proporcionar dicha opción en el Blog. Si alguien quiere comentar algo, me puede encontrar en Mastodon y en Diaspora con el nick de Notxor.

También se ha abierto un grupo en Telegram, para usuarios de esa red.

Disculpen las molestias.