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:
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.
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:
Conste que acabo de reprimir las ganas de llamarlo pichorro o chismático.