Notxor tiene un blog

Defenestrando la vida


Sobre listas y atoms

Hoy toca ver un poco más sobre la programación en elisp. Concretamente cómo funcionan las listas y los tipos de datos que pueden contener. Hablaré también un poco sobre algunas funciones básicas para procesar listas.

El primer programa: “Hola Mundo”

Es tradición en los textos sobre programación incitar al lector a hacer un primer programa muy sencillo que consiste en imprimir por pantalla un mensaje que diga Hola Mundo!. Para no faltar a la tradición, aunque esto ya lo hicimos en el artículo anterior sobre elisp, escribiremos lo siguiente en el buffer *scratch*:

(message "Hola Mundo!")

Es un programa muy sencillo y consiste sólo en usar una función que muestra el mensaje cuando pulsamos C-x C-e. Bueno, vale, era sólo para refrescar un poco la memoria al repasar lo del primer día, vamos con el tema de hoy.

Listas

¿Qué es una lista? Pues básicamente todo lo que se encuentra encerrado entre paréntesis. Es decir, en el ejemplo anterior hemos empleado una lista. Si la observamos bien veremos que consta de dos elementos, el primero es la función message y el segundo la cadena Hola Mundo!, separados por un espacio.

El concepto de lista es central en elisp, como en cualquier otro Lisp ─que recuerdo que significa básicamente procesador de listas... si añadimos que emacs significa editor de macros, podemos decir que elisp significará procesador de listas del editor de macros, o algo así─.

'(uno 2 "tres")        ; Una lista de tres elementos
'()                    ; Una lista vacía

Cómo están construidas las listas

Las listas se construyen encadenando lo que se llaman cons cell. Un cons cell es un objeto1 que consiste en dos partes que se llaman la parte CAR y la parte CDR. Cada una de las partes puede contener cualquier tipo de objeto soportado por Lisp. Un esquema gráfico de una lista sería el siguiente:

diagrama-bloques.png

Obsérvese el último cdr apunta a nil. En Lisp una lista vacía es equivalente a nil. Es decir, las siguientes expresiones son iguales:

'(1 ())
'(1 nil)

Una de las cosas que más puede liar a la gente es el uso del apóstrofe «'». ¿Por qué lo llevan unas listas y otras no? ¿Cuándo hay que ponerlo? Pues es fácil: lo usamos cuando queremos que elisp acepte ese dato tal cual, sin intentar interpretarlo.

Lisp intenta ejecutar como función el primer elemento de una lista utilizando el resto como los parámetros de la misma. Cuando no necesitamos o no queremos que interprete una lista de esa manera se le debe poner el «'», tal que 'elemento en lugar del más largo (quote elemento), que es una lista que devuelve elemento. Hay quien llamaría a eso azúcar sintáctico (cada vez que alguien habla de ese tema me imagino a Celia Cruz cantando eso de «Asssúcar»).

Atoms

Las listas ya hemos visto que se basan en los cons cell y también sabemos cómo funcionan. Algún adelantadillo que lea esto dirá, pero si son las listas enlazadas de toda la vida. Bueno, sí, son listas y están enlazadas pero no me voy a poner a estas alturas a hablar de punteros y otros chismáticos de más bajo nivel. ¿Y esto qué tiene que ver con los Atoms? Pues básicamente un Atom es toda otra estructura o dato (véase la nota nº 1) que Lisp reconoce, que no se basa en los cons cell. Dicho a lo bruto ─y no siendo demasiado preciso─ lo que no es una lista, es un Atom. Por lo que podríamos decir también que los Atoms son pichorros que se pueden almacenar en una lista.

Los hay de muchos tipos: números, cadenas, vectores, hashs, arrays, en fin muchos tipos y sería largo ponerme a enumerar todos. Mejor los dejamos en que existen y cuando los necesitemos nos detenemos a explicarlos con más detalle. Si alguien tiene prisa por conocerlos la ayuda de Emacs está ahí para algo.

Jugando un poco con las listas

Bueno, vamos a ver cómo funcionan los cons cell y las listas con un poco de código.

(cons 'a 'b)                      ; ==> (a . b)
(cons 'a (cons 'b 'c))            ; ==> (a b . c)
(cons 'a (cons 'b (cons 'c nil))) ; ==> (a b c)

Como se puede ver la función cons utiliza un cons cell para endosar en sus huecos los valores que le pasamos. Vamos a imaginarlo gráficamente para ver si nos aclaramos mejor. El gran disléxico Einstein decía: Si no lo puedo dibujar no lo entiendo.

cons-cell-lista.png

La última de las opciones se puede conseguir también con la función list, que utilizará todos los argumentos que le pasemos para formar una lista:

(list 'a 'b 'c) ; ==> (a b c)

También podemos utiliza cons para añadir elementos a una lista por el inicio. Por ejemplo:

(cons 'c (list 'a 'b))  ; ==> (c a b)

Funciones funcionales

Muchos lenguajes de los llamados funcionales utilizan el concepto iniciado por Lisp para obtener la cabecera de una lista y el resto de la lista por separado. Esas funciones son car y cdr.

Primero, para este ejemplo un poco más de información sobre cosas básicas, como dotar de valor a una variable. En Lisp una de las cosas que me desconcertaron al principio fue que las variables y las funciones pueden llamarse igual. En otros derivados de Lisp como scheme, no. Lisp tiene dos espacios de nombres y scheme uno.

set y setq

(set 'primera (list 'a 'b 'c)) ; ==> primera == (a b c)
(setq segunda (list 'd 'e 'f)) ; ==> segunda == (d e f)

Las dos funciones hacen básicamente lo mismo: establecer el valor de una variable. La diferencia entre las dos es que la primera tenemos que marcar el nombre de la variable también con el apóstrofe (quote en inglés) y la segunda lo hace ella por nosotros. La mayoría del código que podemos encontrar en el fichero de configuración de Emacs utiliza alguna de estas dos funciones para establecer valores que luego utiliza una determinada librería o módulo para ajustar su funcionamiento.

car y cdr

Estas dos funciones son la base del trabajo con listas. car devuelve el primer elemento de una lista, por ejemplo:

(car primera)    ; ==> a
(car segunda)    ; ==> d

Por el contrario, cdr nos devuelve una lista con la cola, es decir la lista que contiene el resto del argumento.

(cdr primera)              ; ==> (b c)
(cdr (cdr primera))        ; ==> (c)
(cdr segunda)              ; ==> (e f)
(cdr (cdr (cdr segunda)))  ; ==> () == nil

Conclusión

Hoy hemos avanzado un poco más con el elisp. No mucho, pero lo suficiente como para ir entendiendo cómo funciona. Así a brochazos gordos todo es fácil, luego el diablo lo encontraremos en los detalles.

De momento no hemos hecho nada más que rascar un poquito la superficie del lenguaje. Pero ya se puede observar la belleza del mismo. Todo en él es una lista, llamar a una función es utilizar una lista con el nombre de la misma y los argumentos que necesita, pero los argumentos pueden ser listas que procesan otras listas más internas. Eso hará que aparezcan paréntesis por todos lados, que es lo primero que llama la atención cuando estamos aprendiendo el lenguaje (y la principal crítica de los que no lo conocen... ¿alguien a quien le intimidan los paréntesis puede llamarse programador?( ¡Ooopppsss!

Nota al pie de página:

1

Conste que acabo de reprimir las ganas de llamarlo pichorro o chismático.


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.