Funciones lambda (lo básico)

Las funciones anónimas son procedimientos que no tienen nombre pero que podemos aplicar como cualquier otro procedimiento. En este vídeo las presento y muestro algunos ejemplos de uso: como parámetros de apply, map, o para construir procedimientos que devuelven procedimientos.

Hasta ahora, cuando hemos creado funciones en Racket, siempre les hemos asignado un nombre mediante define. Esto nos permite invocarlas fácilmente usando ese identificador. Sin embargo, en ocasiones no queremos que una función tenga un nombre propio porque solo la vamos a usar una vez dentro de otra función, o simplemente queremos mantener el código más limpio y evitar conflictos de nombres. Para estos casos, Racket nos ofrece las funciones anónimas, conocidas como funciones lambda.

Las funciones lambda son funciones sin nombre que podemos definir y usar directamente sin necesidad de asignarles un identificador. Esto es especialmente útil cuando queremos crear funciones internas que solo se usan en un contexto muy específico y no queremos saturar nuestro espacio de nombres con funciones que no se reutilizan.

Para entenderlo mejor, imaginemos que queremos calcular los perímetros de varios rectángulos. Normalmente, crearíamos una función llamada perimetro que recibe dos lados y devuelve la suma de dos veces cada lado, es decir, el perímetro. Luego, para calcular los perímetros de varias parejas de lados, usaríamos map con esa función y dos listas de lados.

(define (perimetro a b)
  (+ (* 2 a) (* 2 b)))

(define (dame-perimetros as bs)
  (map perimetro as bs))

(dame-perimetros '(3 4 5) '(4 5 6))
; Resultado: (14 18 22)

Esto funciona bien, pero si nuestra base de código crece mucho, tener funciones como perimetro definidas globalmente puede ser innecesario y hasta problemático si queremos definir otra función con el mismo nombre para otra figura, como un triángulo. Aquí es donde las funciones lambda nos ayudan a mantener el código más limpio.

La sintaxis básica de una función lambda en Racket es:

(lambda (param1 param2 ...) cuerpo)

Por ejemplo, una función identidad que devuelve su argumento sería:

(lambda (x) x)

Esta función no tiene nombre, pero podemos invocarla directamente envolviéndola en paréntesis y pasando el argumento:

((lambda (x) x) 5)
; Resultado: 5

Si queremos una función que sume dos números, sería:

((lambda (x y) (+ x y)) 5 4)
; Resultado: 9

Volviendo al ejemplo de los perímetros, podemos reescribir la función dame-perimetros usando una función lambda en lugar de definir perimetro globalmente:

(define (dame-perimetros as bs)
  (map (lambda (x y) (+ (* 2 x) (* 2 y))) as bs))

(dame-perimetros '(3 4 5) '(4 5 6))
; Resultado: (14 18 22)

Así, la función que calcula el perímetro está definida justo donde se usa, sin necesidad de un nombre global. Esto ayuda a evitar conflictos y mantiene el código más organizado.

Además, Racket facilita la legibilidad cuando escribimos funciones lambda con saltos de línea y una indentación adecuada:

(define (dame-perimetros as bs)
  (map (lambda (x y)
         (+ (* 2 x) (* 2 y)))
       as bs))

Otra ventaja interesante de las funciones lambda es que podemos crear funciones que devuelven otras funciones. Por ejemplo, imaginemos que queremos construir una función que genere saludos personalizados. Primero, una función simple que concatena Hola con un nombre:

(define (saludo-nombre nombre)
  (string-append "Hola " nombre))

Si llamamos a (saludo-nombre "Dani"), obtenemos "Hola Dani".

Ahora, si queremos que el saludo sea variable, podemos definir una función que reciba el saludo y el nombre:

(define (saludo saludo-texto nombre)
  (string-append saludo-texto ", " nombre))

Por ejemplo, (saludo "Buenos días" "Dani") devuelve "Buenos días, Dani".

Pero podemos ir más allá y crear una función que, dado un saludo, nos devuelva una función que solo necesite el nombre para saludar. Esto se logra con una función que devuelve una función lambda:

(define (construir-saludo saludo-texto)
  (lambda (nombre)
    (string-append saludo-texto ", " nombre)))

Así, podemos crear saludos personalizados:

(define saludar-hola (construir-saludo "Hola"))
(define saludar-buenos-dias (construir-saludo "Buenos días"))

(saludar-hola "Dani")
; Resultado: "Hola, Dani"

(saludar-buenos-dias "Amigos")
; Resultado: "Buenos días, Amigos"

Esta técnica es muy poderosa porque nos permite generar funciones específicas sobre la marcha, sin necesidad de definir cada una por separado. Podemos crear funciones con menos parámetros o funciones personalizadas que encapsulan ciertos valores.

Aunque aquí hemos visto solo lo básico, las funciones lambda tienen un trasfondo mucho más profundo en la teoría de la computación, conocido como cálculo lambda, que iremos explorando más adelante. Por ahora, entender cómo usarlas para evitar nombres innecesarios, para crear funciones internas y para devolver funciones es un gran paso para escribir código más limpio y funcional en Racket.

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