Introducci贸n
Contenido
La programaci贸n funcional es un paradigma de programaci贸n popular estrechamente vinculado a los fundamentos matem谩ticos de la inform谩tica. Si bien no existe una definici贸n estricta de lo que constituye un lenguaje funcional, consideramos que son lenguajes que utilizan funciones para transformar datos.
Python no es un lenguaje de programaci贸n funcional, pero incorpora algunos de sus conceptos junto con otros paradigmas de programaci贸n. Con Python, es f谩cil escribir c贸digo en un estilo funcional, lo que puede proporcionar la mejor soluci贸n para la tarea en cuesti贸n.
Conceptos de programaci贸n funcional
Los lenguajes funcionales son declarativo idiomas, le dicen a la computadora qu茅 resultado quieren. Esto suele contrastarse con imperativo idiomas que le dicen a la computadora qu茅 pasos tomar para resolver un problema. Python generalmente se codifica de manera imperativa, pero puede usar el estilo declarativo si es necesario.
Algunas de las caracter铆sticas de Python fueron influenciadas por Haskell, un lenguaje de programaci贸n puramente funcional. Para tener una mejor apreciaci贸n de lo que es un lenguaje funcional, veamos las caracter铆sticas de Haskell que pueden verse como caracter铆sticas funcionales deseables:
- Funciones puras – no tienen efectos secundarios, es decir, no cambian el estado del programa. Dada la misma entrada, una funci贸n pura siempre producir谩 la misma salida.
- Inmutabilidad – los datos no se pueden cambiar una vez creados. Tomemos, por ejemplo, la creaci贸n de un
List
con 3 elementos y almacenarlo en una variablemy_list
. Simy_list
es inmutable, no podr谩 cambiar los elementos individuales. Tendr铆as que establecermy_list
a un nuevoList
si desea utilizar valores diferentes. - Funciones de orden superior – las funciones pueden aceptar otras funciones como par谩metros y las funciones pueden devolver nuevas funciones como salida. Esto nos permite abstraer acciones, d谩ndonos flexibilidad en el comportamiento de nuestro c贸digo.
Haskell tambi茅n ha influido en los iteradores y generadores en Python a trav茅s de su carga diferida, pero esa caracter铆stica no es necesaria para un lenguaje funcional.
Programaci贸n funcional en Python
Sin ninguna caracter铆stica o biblioteca especial de Python, podemos comenzar a codificar de una manera m谩s funcional.
Funciones puras
Si desea que las funciones sean puras, no cambie el valor de la entrada ni ning煤n dato que exista fuera del alcance de la funci贸n.
Esto hace que la funci贸n que escribimos sea mucho m谩s f谩cil de probar. Como no cambia el estado de ninguna variable, tenemos la garant铆a de obtener la misma salida cada vez que ejecutamos la funci贸n con la misma entrada.
Creemos una funci贸n pura para multiplicar n煤meros por 2:
def multiply_2_pure(numbers):
new_numbers = []
for n in numbers:
new_numbers.append(n * 2)
return new_numbers
original_numbers = [1, 3, 5, 10]
changed_numbers = multiply_2_pure(original_numbers)
print(original_numbers) # [1, 3, 5, 10]
print(changed_numbers) # [2, 6, 10, 20]
La lista original de numbers
no se modifican y no hacemos referencia a ninguna otra variable fuera de la funci贸n, por lo que es puro.
Inmutabilidad
Alguna vez tuvo un error en el que se pregunt贸 c贸mo se convirti贸 una variable que estableci贸 en 25 None
? Si esa variable fuera inmutable, el error se habr铆a arrojado donde se estaba cambiando la variable, no donde el valor cambiado ya afectaba al software; la causa ra铆z del error se puede encontrar antes.
Python ofrece algunos tipos de datos inmutables, uno popular es el Tuple
. Comparemos la tupla con una lista, que es mutable:
mutable_collection = ['Tim', 10, [4, 5]]
immutable_collection = ('Tim', 10, [4, 5])
# Reading from data types are essentially the same:
print(mutable_collection[2]) # [4, 5]
print(immutable_collection[2]) # [4, 5]
# Let's change the 2nd value from 10 to 15
mutable_collection[1] = 15
# This fails with the tuple
immutable_collection[1] = 15
El error que ver铆a es: TypeError: 'tuple' object does not support item assignment
.
Ahora, hay un escenario interesante donde un Tuple
puede parecer un objeto mutable. Por ejemplo, si quisi茅ramos cambiar la lista en immutable_collection
de [4, 5]
a [4, 5, 6]
, puede hacer lo siguiente:
immutable_collection[2].append(6)
print(immutable_collection[2]) # [4, 5, 6]
Esto funciona porque un List
es un objeto mutable. Intentemos volver a cambiar la lista a [4, 5]
.
immutable_collection[2] = [4, 5]
# This throws a familiar error:
# TypeError: 'tuple' object does not support item assignment
Falla tal como lo esper谩bamos. Si bien podemos cambiar el contenido de un objeto mutable en un Tuple
, no podemos cambiar la referencia al objeto mutable que est谩 almacenado en la memoria.
Funciones de orden superior
Recuerde que las funciones de orden superior aceptan una funci贸n como argumento o devuelven una funci贸n para su posterior procesamiento. Ilustremos lo simple que se pueden crear ambos en Python.
Considere una funci贸n que imprime una l铆nea varias veces:
def write_repeat(message, n):
for i in range(n):
print(message)
write_repeat('Hello', 5)
驴Y si quisi茅ramos escribir en un archivo 5 veces o registrar el mensaje 5 veces? En lugar de escribir 3 funciones diferentes en bucle, podemos escribir 1 funci贸n de orden superior que acepte esas funciones como argumento:
def hof_write_repeat(message, n, action):
for i in range(n):
action(message)
hof_write_repeat('Hello', 5, print)
# Import the logging library
import logging
# Log the output as an error instead
hof_write_repeat('Hello', 5, logging.error)
Ahora imagine que tenemos la tarea de crear funciones que incrementen los n煤meros en una lista en 2, 5 y 10. Comencemos con el primer caso:
def add2(numbers):
new_numbers = []
for n in numbers:
new_numbers.append(n + 2)
return new_numbers
print(add2([23, 88])) # [25, 90]
Si bien es trivial escribir add5
y add10
funciones, es obvio que operar铆an de la misma manera: recorriendo la lista y agregando el incrementador. Entonces, en lugar de crear muchas funciones de incremento diferentes, creamos 1 funci贸n de orden superior:
def hof_add(increment):
# Create a function that loops and adds the increment
def add_increment(numbers):
new_numbers = []
for n in numbers:
new_numbers.append(n + increment)
return new_numbers
# We return the function as we do any other value
return add_increment
add5 = hof_add(5)
print(add5([23, 88])) # [28, 93]
add10 = hof_add(10)
print(add10([23, 88])) # [33, 98]
Las funciones de orden superior dan flexibilidad a nuestro c贸digo. Al abstraer qu茅 funciones se aplican o devuelven, obtenemos un mayor control del comportamiento de nuestro programa.
Python proporciona algunas funciones de orden superior integradas 煤tiles, lo que hace que trabajar con secuencias sea mucho m谩s f谩cil. Primero veremos las expresiones lambda para utilizar mejor estas funciones integradas.
Expresiones lambda
Una expresi贸n lambda es una funci贸n an贸nima. Cuando creamos funciones en Python, usamos el def
palabra clave y darle un nombre. Las expresiones lambda nos permiten definir una funci贸n mucho m谩s r谩pidamente.
Creemos una funci贸n de orden superior hof_product
que devuelve una funci贸n que multiplica un n煤mero por un valor predefinido:
def hof_product(multiplier):
return lambda x: x * multiplier
mult6 = hof_product(6)
print(mult6(6)) # 36
La expresi贸n lambda comienza con la palabra clave lambda
seguido de los argumentos de la funci贸n. Despu茅s de los dos puntos est谩 el c贸digo devuelto por lambda. Esta capacidad de crear funciones “sobre la marcha” se utiliza mucho cuando se trabaja con funciones de orden superior.
Hay mucho m谩s sobre las expresiones lambda que cubrimos en nuestro art铆culo Funciones Lambda en Python si desea m谩s informaci贸n.
Funciones de orden superior integradas
Python ha implementado algunas funciones de orden superior de uso com煤n de lenguajes de programaci贸n funcional que facilitan mucho el procesamiento de objetos iterables como listas e iteradores. Por razones de eficiencia de espacio / memoria, estas funciones devuelven un iterator
en lugar de una lista.
Mapa
los map
funci贸n nos permite aplicar una funci贸n a cada elemento en un objeto iterable. Por ejemplo, si tuvi茅ramos una lista de nombres y quisi茅ramos agregar un saludo a las cadenas, podemos hacer lo siguiente:
names = ['Shivani', 'Jason', 'Yusef', 'Sakura']
greeted_names = map(lambda x: 'Hi ' + x, names)
# This prints something similar to: <map object at 0x10ed93cc0>
print(greeted_names)
# Recall, that map returns an iterator
# We can print all names in a for loop
for name in greeted_names:
print(name)
Filtrar
los filter
La funci贸n prueba cada elemento de un objeto iterable con una funci贸n que devuelve True
o False
, solo conservando aquellos que eval煤an True
. Si tuvi茅ramos una lista de n煤meros y quisi茅ramos mantener aquellos que son divisibles por 5 podemos hacer lo siguiente:
numbers = [13, 4, 18, 35]
div_by_5 = filter(lambda num: num % 5 == 0, numbers)
# We can convert the iterator into a list
print(list(div_by_5)) # [35]
Combinatorio map
y filter
Como cada funci贸n devuelve un iterador, y ambas aceptan objetos iterables, 隆podemos usarlos juntos para algunas manipulaciones de datos realmente expresivas!
# Let's arbitrarily get the all numbers divisible by 3 between 1 and 20 and cube them
arbitrary_numbers = map(lambda num: num ** 3, filter(lambda num: num % 3 == 0, range(1, 21)))
print(list(arbitrary_numbers)) # [27, 216, 729, 1728, 3375, 5832]
La expresi贸n en arbitrary_numbers
se puede dividir en 3 partes:
range(1, 21)
es un objeto iterable que representa n煤meros del 1, 2, 3, 4 … 19, 20.filter(lambda num: num % 3 == 0, range(1, 21))
es un iterador para la secuencia num茅rica 3, 6, 9, 12, 15 y 18.- Cuando est谩n en cubos por el
map
expresi贸n podemos obtener un iterador para la secuencia num茅rica 27, 216, 729, 1728, 3375 y 5832.
Lista de comprensiones
Una caracter铆stica popular de Python que aparece de manera destacada en los lenguajes de programaci贸n funcionales son las listas por comprensi贸n. Como el map
y filter
funciones, listas por comprensi贸n nos permiten modificar datos de forma concisa y expresiva.
Probemos nuestros ejemplos anteriores con map
y filter
con listas por comprensi贸n en su lugar:
# Recall
names = ['Shivani', 'Jan', 'Yusef', 'Sakura']
# Instead of: map(lambda x: 'Hi ' + x, names), we can do
greeted_names = ['Hi ' + name for name in names]
print(greeted_names) # ['Hi Shivani', 'Hi Jason', 'Hi Yusef', 'Hi Sakura']
Una lista de comprensi贸n b谩sica sigue este formato: [result for
singular-element in
list-name].
Si nos gustar铆a filtrar objetos, entonces necesitamos usar el if
palabra clave:
# Recall
numbers = [13, 4, 18, 35]
# Instead of: filter(lambda num: num % 5 == 0, numbers), we can do
div_by_5 = [num for num in numbers if num % 5 == 0]
print(div_by_5) # [35]
# We can manage the combined case as well:
# Instead of:
# map(lambda num: num ** 3, filter(lambda num: num % 3 == 0, range(1, 21)))
arbitrary_numbers = [num ** 3 for num in range(1, 21) if num % 3 == 0]
print(arbitrary_numbers) # [27, 216, 729, 1728, 3375, 5832]
Cada map
y filter
La expresi贸n se puede expresar como una lista de comprensi贸n.
Algunas cosas a considerar
Es bien sabido que el creador de Python, Guido van Rossum, no ten铆a la intenci贸n de que Python tuviera caracter铆sticas funcionales, pero apreci贸 algunos de los beneficios que su introducci贸n ha tra铆do al lenguaje. Habl贸 de la historia de las caracter铆sticas del lenguaje de programaci贸n funcional en uno de sus publicaciones de blog. Como resultado, las implementaciones del lenguaje no se han optimizado para las caracter铆sticas de programaci贸n funcional.
Adem谩s, la comunidad de desarrolladores de Python no fomenta el uso de la amplia gama de funciones de programaci贸n funcional. Si estuviera escribiendo c贸digo para que lo revisara la comunidad global de Python, escribir铆a listas por comprensi贸n en lugar de usar map
o filter
. Lambdas se usar铆a m铆nimamente como nombrar铆a sus funciones.
En su int茅rprete de Python, ingrese import this
y ver谩 “El Zen de Python”. Python generalmente alienta a que el c贸digo se escriba de la manera m谩s obvia posible. Idealmente, todo el c贸digo debe escribirse de una manera: la comunidad no cree que deba tener un estilo funcional.
Conclusi贸n
La programaci贸n funcional es un paradigma de programaci贸n con software compuesto principalmente por funciones que procesan datos a lo largo de su ejecuci贸n. Aunque no existe una definici贸n 煤nica de lo que es la programaci贸n funcional, pudimos examinar algunas caracter铆sticas destacadas de los lenguajes funcionales: funciones puras, inmutabilidad y funciones de orden superior.
Python nos permite codificar en un estilo declarativo funcional. Incluso tiene soporte para muchas caracter铆sticas funcionales comunes como Lambda Expressions y el map
y filter
funciones.
Sin embargo, la comunidad de Python no considera que el uso de t茅cnicas de programaci贸n funcional sea la mejor pr谩ctica en todo momento. Aun as铆, hemos aprendido nuevas formas de resolver problemas y, si es necesario, podemos resolver problemas aprovechando la expresividad de la programaci贸n funcional.