A.1.4 Tipos de datos compuestos de Scheme

También existen tipos de datos compuestos en Scheme. Entre los tipos más usados en la programación de LilyPond se encuentran las parejas, las listas, las listas-A y las tablas de hash.


Parejas

El tipo fundacional de datos compuestos de Scheme es la pareja. Como se espera por su nombre, una pareja son dos valores unidos en uno solo. El operador que se usa para formar una pareja se llama cons.

 
guile> (cons 4 5)
(4 . 5)
guile>

Observe que la pareja se imprime como dos elementos rodeados por paréntesis y separados por un espacio, un punto (.) y otro espacio. El punto no es un punto decimal, sino más bien un indicador de pareja.

Las parejas también se pueden introducir como valores literales precediéndolos de un carácter de comilla simple o apóstrofo.

 
guile> '(4 . 5)
(4 . 5)
guile>

Los dos elementos de una pareja pueden ser cualquier valor válido de Scheme:

 
guile> (cons #t #f)
(#t . #f)
guile> '("bla-bla" . 3.1415926535)
("bla-bla" . 3.1415926535)
guile>

Se puede accede al primero y segundo elementos de la pareja mediante los procedimientos de Scheme car y cdr, respectivamente.

 
guile> (define mipareja (cons 123 "Hola")
… )
guile> (car mipareja)
123
guile> (cdr mipareja)
"Hola"
guile>

Nota: cdr se pronuncia "could-er", según Sussman y Abelson, véase http://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book-Z-H-14.html#footnote_Temp_133


Listas

Una estructura de datos muy común en Scheme es la lista. Formalmente, una lista ‘bien hecha’ se define como la lista vacía, representada como '() y con longitud cero, o bien como una pareja cuyo cdr es a su vez una lista más corta.

Existen muchas formas de crear listas. Quizá la más común es con el procedimiento list:

 
guile> (list 1 2 3 "abc" 17.5)
(1 2 3 "abc" 17.5)

La representación de una lista como elementos individuales separados por espacios y encerrada entre paréntesis es realmente una forma compacta de las parejas con punto que constituyen la lista, donde el punto e inmediatamente un paréntesis de apertura se suprimen junto al paréntesis de cierre correspondiente. Sin esta compactación, la salida habría sido

 
(1 . (2 . (3 . ("abc" . (17.5 . ())))))

De igual forma que con la salida, una lista puede escribirse (después de haber añadido un apóstrofo para evitar su interpretación como una llamada de función) como una lista literal encerrando sus elementos entre paréntesis:

 
guile> '(17 23 "fulano" "mengano" "zutano")
(17 23 "fulano" "mengano" "zutano")

Las listas son una parte fundamental de Scheme. De hecho, Scheme se considera un dialecto de Lisp, donde ‘lisp’ es una abreviatura de ‘List Processing’ (proceso de listas). Todas las expresiones de Scheme son listas.


Listas asociativas (listas-A)

Un tipo especial de listas son las listas asociativas o listas-A. Se puede usar una lista-A para almacenar datos para su fácil recuperación posterior.

Las listas-A son listas cuyos elementos son parejas. El car de cada elemento se llama clave, y el cdr de cada elemento se llama valor. El procedimiento de Scheme assoc se usa para recuperar un elemento de la lista-A, y cdr se usa para recuperar el valor:

 
guile> (define mi-lista-a '((1  . "A") (2 . "B") (3 . "C")))
guile> mi-lista-a
((1 . "A") (2 . "B") (3 . "C"))
guile> (assoc 2 mi-lista-a)
(2 . "B")
guile> (cdr (assoc 2 mi-lista-a))
"B"
guile>

Las listas-A se usan mucho en LilyPond para almacenar propiedades y otros datos.


Tablas de hash

Estructuras de datos que se utilizan en LilyPond de forma ocasional. Una tabla de hash es similar a una matriz, pero los índices de la matriz pueden ser cualquier tipo de valor de Scheme, no sólo enteros.

Las tablas de hash son más eficientes que las listas-A si hay una gran cantidad de datos que almacenar y los datos cambian con muy poca frecuencia.

La sintaxis para crear tablas de hash es un poco compleja, pero veremos ejemplos de ello en el código fuente de LilyPond.

 
guile> (define h (make-hash-table 10))
guile> h
#<hash-table 0/31>
guile> (hashq-set! h 'key1 "val1")
"val1"
guile> (hashq-set! h 'key2 "val2")
"val2"
guile> (hashq-set! h 3 "val3")
"val3"

Los valores se recuperan de las tablas de hash mediante hashq-ref.

 
guile> (hashq-ref h 3)
"val3"
guile> (hashq-ref h 'key2)
"val2"
guile>

Las claves y los valores se recuperan como una pareja con hashq-get-handle. Ésta es la forma preferida, porque devuelve #f si no se encuentra la clave.

 
guile> (hashq-get-handle h 'key1)
(key1 . "val1")
guile> (hashq-get-handle h 'frob)
#f
guile>

LilyPond — Extender v2.23.4 (rama de desarrollo).