Revisitando las definiciones

Volvemos a hablar del define para mencionar conceptos útiles: cómo usar APPLY, cómo hacer funciones que acepten múltiples parámetros y algunos trucos con los nombres de funciones.

Revisitar las funciones en Racket nos permite profundizar en conceptos que, aunque básicos, tienen mucha riqueza y flexibilidad. Ya sabemos que una función en Racket se define con define, un nombre y una serie de parámetros, y que puede realizar operaciones sencillas como doblar un número. Pero, ¿qué pasa cuando queremos que una función acepte un número variable de parámetros o que trabaje eficientemente con listas?

El número de parámetros que una función acepta se llama aridad, y en muchos lenguajes, funciones con el mismo nombre pero distinta aridad pueden coexistir. En Racket, podemos crear funciones que acepten un número indefinido de parámetros, lo que nos da mucha flexibilidad. Por ejemplo, imaginemos que queremos calcular la media de varios números. Una primera aproximación sería definir una función que acepte dos parámetros y divida su suma entre dos. Esto funciona bien para dos números, pero falla si intentamos pasar tres o más.

Una solución es definir funciones para cada número de parámetros, pero esto no es escalable ni elegante. En cambio, podemos usar listas para manejar cualquier cantidad de datos. Por ejemplo, podemos definir una función recursiva que sume los elementos de una lista. Si la lista está vacía, devuelve cero; si no, suma el primer elemento con la suma del resto de la lista. Así, sumar una lista como (list 1 2 3 4) nos devuelve 10.

Con esta función de suma, podemos definir la media como la suma de los elementos dividida por la longitud de la lista. Esto nos permite calcular la media de cualquier cantidad de números pasados en una lista. Sin embargo, esta solución tiene un problema: si la lista está vacía, intentamos dividir entre cero, lo que genera un error.

Además, definir funciones específicas para sumar, multiplicar o concatenar listas puede ser repetitivo. Aquí es donde entra la función apply. Esta función toma como primer argumento otra función y como segundo argumento una lista de parámetros, y aplica esa función a los elementos de la lista como si fueran argumentos individuales. Por ejemplo, apply + (list 1 2 3 4 5) devuelve 15, porque suma todos los elementos de la lista.

Esto nos permite simplificar nuestra función de suma de lista a simplemente apply + lista. De esta forma, podemos reutilizar apply con cualquier función que acepte múltiples argumentos, como multiplicación o concatenación, sin tener que escribir funciones recursivas específicas para cada operación.

Pero, ¿cómo hacemos para que una función acepte directamente un número variable de parámetros sin tener que pasarlos en una lista? En Racket, podemos definir funciones con aridad múltiple usando la sintaxis de punto (.) en la lista de parámetros. Por ejemplo:

(define (media . lista)
  (/ (apply + lista) (length lista)))

Aquí, lista es una lista que contiene todos los argumentos pasados a la función. Así, podemos llamar a (media 1 2 3 4 5) y la función sumará todos los argumentos y dividirá entre su cantidad, calculando la media sin necesidad de pasar explícitamente una lista.

Además, podemos combinar esta técnica con apply para mantener la flexibilidad y expresividad en nuestras funciones.

Un ejemplo interesante que surge de esta flexibilidad es la posibilidad de que el nombre de una función sea en sí mismo una expresión que devuelve otra función. Por ejemplo, podemos definir una función que, según un valor booleano, devuelva la función suma o la función resta:

(define (sumamos-o-restamos boo a b)
  ((if boo + -) a b))

Si llamamos a (sumamos-o-restamos #t 1 2), obtenemos 3, y si llamamos a (sumamos-o-restamos #f 1 2), obtenemos -1. Esto es posible porque en Racket los identificadores de funciones son expresiones que pueden evaluarse y usarse como cualquier otro valor, lo que abre muchas posibilidades para la programación funcional.

Incluso podemos definir funciones que reciban como parámetro otra función y la apliquen a ciertos valores, como en este ejemplo:

(define (dime xab)
  (xab 2 3))

Si llamamos a (dime *), la función multiplicará 2 por 3 y devolverá 6.

En definitiva, en Racket las funciones son objetos de primera clase que podemos manipular, pasar como argumentos, devolver como resultados y combinar con técnicas como listas, recursión y apply para crear programas más flexibles y expresivos. Esto nos prepara para explorar en profundidad las funciones anónimas o lambda, que son fundamentales en la programación funcional y que veremos en próximos encuentros.

Lista de reproducción
  1. 1
    Introducción al entorno
    16 minutos
  2. 2
    Definiciones, funciones y comentarios
    16 minutos
  3. 3
    Tipos de datos
    23 minutos
  4. 4
    Listas: manipulación, iteración y recursión
    25 minutos
  5. 5
    Todo sobre las condicionales
    17 minutos
  6. 6
    Revisitando las definiciones
    15 minutos
  7. 7
    Funciones lambda (lo básico)
    13 minutos
  8. 8
    Asignaciones con let y let*
    9 minutos