cyberlab
notes/Programación Funcional

$ cat programación-funcional.md

Programación Funcional

Jul 2, 2026 Aprende programación funcional
#programacion funcional#programación#python#lambda#streams

Introducción

En Python las funciones son objetos de primera clase (eso quiere decir que el lenguaje las va tratar con total libertad), estos objetos pueden pararse como argumentos de una función, al igual que el resto de los tipos de datos.

>>> def aplica(funcion, argumento):
...   return funcion(argumento)

>>> def cuadrado(n):
...   return n*n

>>> def cubo(n):
...   return n**3

>>> aplica(cuadrado, 5)
25
>>> aplica(cubo, 5)
125

Funciones anónimas (funcionas Lambda)

Existe un tipo especial de funciones que no tienen un nombre asociado y se conocen como las funciones anónimas o funciones lambda la sintaxis para definir una función anónima es:

lambda <parámetros> : <expresión>

Estas funciones se suelen asociar a una variable o parámetro desde la que hacer la llamada:

>>> area = lambda base, altura : base * altura
>>> area(4,5)
10

Comparemos con una función normal

#función normal
>>> def cuadrado(n):
      return n ** 2

#función lambda equivalente
>>> cuadrado = lambda n : n**2

>>> print(cuadrado(5))

25

fíjate que no existe un return explícito, el resultado de la expresión se devuelve automáticamente. Y solo puede se una expresión, no varias líneas de código ni statements como if/else completo (aunque podrías usar el operador ternario)

Varios argumentos

>>> suma = lambda a, b: a+b
>>> print(suma(5,4))
7

Casi nunca le vas a asignar una variable como en el ejemplo de arriba (para eso es mejor usar def). El uso real es cuando necesitas pasar una función pequeña como argumento de otra función sin ensuciar el código con un def aparte, un ejemplo clásico es sorted()

>>> personas = [("Ana", 25), ("Luis", 19), ("Marta", 34)]

>>> personas_ordenadas = sorted(pesonas, key=lambda persona: persona[1])
>>> print(personas_ordenadas)
[('Luis', 19), ('Ana', 25), ('Marta', 34)]

Sin el uso de lambda tendrías que escribir algo como:

>>> def obtener_edad(persona):
...  return persona[1]

>>> personas_ordenadas = sorted(personas, key=obtener_edad)

Funciona igual, pero para algo tan chico, la lambda evita el peso extra

Aplicar una función a todos los elementos de una colección iterable (map)

map(f,c): Devuelve un objeto iterable con los resultados de aplicar la función f a los elementos de la colección c. Si la función f requiere n argumentos, entonces, deben pararse n colecciones con los argumentos. Para convertir el objeto en una lista, tupla o diccionario hay que aplicar explícitamente las funciones list(), tuple() o dic() respectivamente. Es básicamente una función built-in de Python que aplica una función a cada elemento de uno o más iterables, y devuelve un objeto map, que es un iterador lazy, es decir, no calcula nada hasta que se lo pides.

Sintaxis

map(función, iterable, *iterables_adicionales)
>>> def cuadrado(n):
...   return n*n

>>>list(map(cuadrado, [1,2,3]))
[1,4,9]

# En el caso de una dupla como el área de un rectángulo
>>> def rectangulo(a, b):
...   return a*b
>>> tuple(map(rectangulo, (1,2,3), (4,5,6)))
(4,10,18)

El objeto map es un iterador, no una lista

>>>numeros = [1,2,3,4]
>>> resultado = map(lambda x : x ** 2, numeros)

>>> print(resultado)
<map objet at 0xf8b1c05ad90> -- #Aún no es una lista

map solo ‘recuerda’ qué función y qué iterable usar. El cálculo real se hace elemento por elemento, cuando iteras sobre él:

>>> print(list(resultado))
[1,4,9,16]

Algo a tener en cuenta es que consume un elemento a la vez, si volvemos a hacer print veremos una lista vacía:

>>> resultado = map(lambda x:x ** 2, [1,2,3])
>>> print(resultado)
[1,4,9]
>>> print(resultado)
[] # <- se agotó

map() es lazy

Como no calculamos nada hasta que se pide o se llama, es eficiente en memoria para colecciones realmente grandes ya que no consume memoria, va calculando uno a uno:

>>> numeros_grandes = range(10000000)
>>> cuadrados = map(lambda x:x**2, numeros_grandes)

>>> for c in cuadrados:
...     if c > 100:
...         break
...     print(c)
...
0
1
4
9
16
25
36
49
64
81
100

Si hubiéramos escrito [x**2 for x in numeros_grandes] de entrada, Python calcula los 10 millones de cuadrados y los guarda en memoria, aunque solo necesites los primeros 3

Filtrar los elementos de una colección iterable filter()

filter(f, c) : Devuelve un objeto iterable con los elementos de la colección c que devuelven True al aplicarles la función f. Para convertir el objeto en una lista, tupla o diccionario hay que aplicar explícitamente las funciones list(), tuple() o dic() respectivamente, como en el caso de map().

OJO!

f debe ser una función que recibe un argumento y devuelve un valor booleano (True o False)

>>> def par(n):
...     return n%2==0
...
>>> list(filter(par, range(10)))
[0, 2, 4, 6, 8]

Combinar los elementos de varias colecciones iterables zip()

zip(c1,c2, ...): Devuelve un objeto iterable cuyos elementos son tuplas formadas por los elementos que ocupan la misma posición en las colecciones c1, c2, etc. El número de elementos de las tuplas es el número de colecciones que se pasen. Es la herramienta ideal cuando tenemos dos o más colecciones relacionadas y quieres recorrerlas a la par o en paralelo.

Sintaxis

zip(iterable1, iterable2, ...)
>>> nombres = ["Fernando", "Luis", "Adrián"]
>>> edades = [25, 19, 45]
>>>
>>> for nombre, edad in zip(nombres, edades):
...     print(f"{nombre} tiene {edad} años")
...
Fernando tiene 25 años
Luis tiene 19 años
Adrián tiene 45 años

El uso más común es este, iterar en paralelo, es mucho más limpio que la alternativa de iterar por índices, además se puede escalar a mas de 2 iterables:

>>> nombres = ["Ana", "Luis"]
>>> edades = [19, 25]
>>> ciudades = ["Monterrey", "CDMX"]

>>> for nombre, edad, ciudad in zip(nombres, edades, ciudades):
...     print(f"{nombre}, {edad} años, vive en {ciudad}")
...
Ana, 19 años, vive en Monterrey
Luis, 25 años, vive en CDMX

Algo que es importante de zip() es que se va a detener en el iterable más corto sin dar error, ignora lo que sobra en las listas.

>>> a = [1,2,3]
>>> b = [1,2,3,4,5]

>>> print(list(zip(a,b)))
[(1, 1), (2, 2), (3, 3)]

Si quieres que en lugar de cortar la linea ponga un placeholder, puedes usar itertools.zip_longest:

>>> from itertools import zip_longest

>>> print(list(zip_longest(a,b,fillvalue="VACÍO")))
[(1, 1), (2, 2), (3, 3), ('VACÍO', 4), ('VACÍO', 5)]