Programaci贸n funcional en Python

    Introducci贸n

    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 variable my_list. Si my_list es inmutable, no podr谩 cambiar los elementos individuales. Tendr铆as que establecer my_list a un nuevo List 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.

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *