En Racket, como en muchos lenguajes de programación, los tipos de datos son fundamentales para construir programas flexibles y eficientes. Podemos dividirlos en dos grandes grupos: los tipos básicos, que son comunes a la mayoría de los lenguajes, y los tipos especiales propios de Racket y de la familia Lisp en general.
Empezando por los tipos básicos, los booleanos son los más sencillos y conocidos. En Racket, representamos el valor verdadero con #t y el falso con #f. Estas son las formas canónicas, aunque también podemos usar las constantes True y False si preferimos una nomenclatura más legible. Los booleanos se utilizan en operaciones lógicas como or y and. Por ejemplo, or devuelve verdadero si alguno de sus argumentos es verdadero, mientras que and solo devuelve verdadero si todos sus argumentos lo son. Además, la estructura condicional if es fundamental: recibe tres parámetros, donde el primero es una condición booleana, y dependiendo de si es verdadero o falso, devuelve el segundo o el tercer parámetro respectivamente.
En cuanto a los números, Racket distingue entre números exactos e inexactos. Los números exactos son aquellos cuyo valor conocemos con certeza, como los enteros, las fracciones y los números imaginarios exactos. Por ejemplo, podemos escribir fracciones directamente como 2/5 o números imaginarios como 5+3i. Para comprobar si un número es exacto, usamos la función exact?, que devuelve verdadero si el número es exacto y falso en caso contrario.
Los números inexactos son aquellos que no conocemos con total precisión, principalmente los números decimales que pueden tener problemas de representación en la CPU. Por ejemplo, 5.5 es un número inexacto. También se consideran inexactos los números infinitos, como +inf.0 o -inf.0, y los números imaginarios que involucran valores inexactos. Los números exponenciales, como 5.25e8 o -2.86e-5, también entran en esta categoría. Para verificar si un número es inexacto, usamos la función inexact?. Independientemente de si un número es exacto o inexacto, la función number? nos indica si un valor es numérico.
Una característica curiosa de Racket es que permite trabajar con números de tamaño arbitrario, sin los límites habituales de 32 o 64 bits. Esto significa que podemos realizar operaciones con números extremadamente grandes, y aunque el procesamiento pueda tardar más, siempre obtendremos un resultado.
Pasando a los caracteres, en Racket trabajamos con codificación Unicode, lo que nos permite representar cualquier carácter de los más de 65,000 disponibles. Un carácter se escribe con #\ seguido del símbolo, por ejemplo, #\A para la letra A mayúscula. También podemos usar la notación Unicode con #\u seguido de cuatro dígitos hexadecimales, como #\u01B para un carácter específico. Para comprobar si un valor es un carácter, usamos la función char?.
Las cadenas de caracteres son secuencias de caracteres agrupados y se escriben entre comillas. Por ejemplo, "OLA" es una cadena de cuatro caracteres. Para mostrar cadenas en pantalla, es preferible usar la función display, que imprime la cadena sin las comillas, haciendo la salida más limpia. Si queremos incluir comillas dentro de una cadena, debemos escaparlas con una barra invertida, como en "él me dijo \"sí\"". También podemos usar caracteres especiales escapados, como el salto de línea con \n, para formatear mejor el texto.
Ahora, entrando en los tipos especiales de Racket, encontramos los pares y las listas enlazadas, que son estructuras fundamentales en Lisp y sus dialectos. Un par es una estructura que contiene dos elementos, accesibles como primero y segundo. Para construir un par usamos la función cons, por ejemplo, (cons 1 2) crea un par con 1 y 2. La representación textual de un par es (1 . 2), y para evitar que Racket lo interprete como una función, lo precedemos con una comilla simple: '(1 . 2). También podemos usar la notación abreviada 1 . 2 para crear pares.
Para acceder a los elementos de un par, usamos las funciones car para el primer elemento y cdr para el segundo. Los pares pueden contener cualquier tipo de dato, incluso otros pares, lo que permite construir estructuras complejas y anidadas. Por ejemplo, podemos tener un par cuyo primer elemento es otro par, y así sucesivamente.
Las listas enlazadas son una estructura recursiva construida a partir de pares. Son especialmente útiles cuando no conocemos de antemano la cantidad de elementos que vamos a manejar, como en el caso de almacenar notas de alumnos en un programa. Una lista enlazada es una secuencia de elementos donde cada elemento apunta al siguiente, y el final de la lista se indica con un valor especial llamado null, nil o sin, que señala que no hay más elementos.
Visualmente, podemos imaginar una lista como un collar de eslabones, donde cada eslabón es un elemento y una referencia al siguiente. Por ejemplo, una lista con los elementos A, B, C y D se representa como un par cuyo primer elemento es A y cuyo segundo elemento es otra lista que comienza con B, y así sucesivamente hasta llegar a null.
En Racket, podemos construir listas manualmente usando cons y null, pero también existe una forma más sencilla con la función list, que recibe cualquier cantidad de elementos y devuelve la lista correspondiente. Por ejemplo, (list 1 2 3) crea una lista con esos tres elementos.
Para trabajar con listas, podemos usar car para obtener el primer elemento y cdr para obtener la lista restante. Al aplicar cdr repetidamente, podemos recorrer toda la lista hasta llegar a null. Además, Racket ofrece funciones para comprobar si un valor es un par con pair? o si es una lista con list?. Es importante tener en cuenta que toda lista es un par, pero no todo par es una lista, ya que un par puede tener cualquier valor en su segundo elemento, no necesariamente otra lista o null.
Estas estructuras son la base para muchas operaciones en Racket, como aplicar funciones a cada elemento, filtrar elementos o realizar recursiones. En próximos momentos, exploraremos más a fondo cómo manipular listas y otros tipos de datos para aprovechar al máximo las capacidades de Racket.