Introducción a la programación funcional en Python

Desde hace unos años se ha vuelto a poner de moda trabajar con programación funcional, como si las nuevas generaciones hubieran re-descubierto las ventajas frente a la programación orientada a objetos o empezaran a tomar consciencia de las limitaciones en su forma de trabajar. Por ello mismo me parece interesante que resolvamos algunas dudas como ¿qué es? ¿para que sirve? ¿por donde puedo empezar? Y si es con Python, mucho mejor.

¿Qué significa que Python es multiparadigma?

Según :

“Python es un lenguaje de programación interpretado cuya filosofía hace hincapié en la legibilidad de su código. Se trata de un lenguaje de programación multiparadigma, ya que soporta orientación a objetos, programación imperativa y, en menor medida, programación funcional.”

Quiere decir que acepta diversas formar de trabajar con el lenguaje. Básicamente existen 3 paradigmas predominantes, o dicho de otra manera, 3 formatos globales de organizar un código.

Si buscar profundizar en sus diferencias disponéis del libro Arquitectura limpia de Robert C. Martin (tio Sam).

Principios de la programación funcional

¿Qué te aporta como programador?

Te estarás diciendo en estos momentos: “Ya se programación orientada a objetos, ¿que gano aprendiendo técnicas funcionales?”.

Ejemplos

Quiero adelantar que solo voy a enseñar unos ejemplos básicos para iniciarse, la punta de un Iceberg. Para los curiosos quedan todo un océano de soluciones y herramientas propias de lenguajes funcionales que Python no conoce o debe ser extendido con librerías. Vamos a tratar funciones de orden superior: lambda, closures, filter, map y reduce.

lambda

Los lenguajes funcionales tienen su origen en la lógica matemática y el cálculo lambda, mientras que los lenguajes de programación imperativos abrazan el modelo de cálculo basado en el estado (inventado por Alan Turing).

¿Qué son los Lambdas en Python? En pocas palabras: pequeñas funciones anónimas, restrictivas y concisas.

Así haríamos en una función tradicional.

def incrementar(x):
    return x + 1

Podéis observar que su objetivo es incrementar un número. Y de este modo se construiría en un lambda.

lambda x: x + 1

Es únicamente una expresión, no la puedes utilizar; pero hay 2 formas de sacarle partido.

Con paréntesis.

(lambda x: x + 1)(9)
# 10

O guardando en una variable.

incremento = lambda x: x + 1
incremento(24)
# 25

closures

Recordar el concepto de Funciones de primera clase. Podemos usar funciones dentro de otras funciones y además funciones que devuelvan otras.

# Función que devuelve una función
def construir_multiplos(factor):
    def interno(valor):
        return valor * factor
    return interno

# Guardamos la expresión
multiplos_de_2 = construir_multiplos(2)
multiplos_de_7 = construir_multiplos(7)

# Evaluamos

multiplos_de_2(10)
# 20

multiplos_de_7(2)
# 14

filter()

Para los siguientes ejemplos usaré un super diccionario con 4 elementos.

superheroes = [
    {
        "nombre": "Batman",
        "editorial": "DC Comics",
        "alter_ego": "Bruce Wayne",
        "primera_aparicion": "Detective Comics #27"
    },
    {
        "nombre": "Superman",
        "editorial": "DC Comics",
        "alter_ego": "Kal-El",
        "primera_aparicion": "Action Comics #1"
    },
    {
        "nombre": "Spider Man",
        "editorial": "Marvel Comics",
        "alter_ego": "Peter Parker",
        "primera_aparicion": "Amazing Fantasy #15"
    },
    {
        "nombre": "Hulk",
        "editorial": "Marvel Comics",
        "alter_ego": "Bruce Banner",
        "primera_aparicion": "The Incredible Hulk #1"
    }
]

Empecemos con filter. Su objetivo es convertir un elemento iterable (como una tupla o lista), en otra pero de igual o inferior tamaño; filtrando por lo que quieras.

Su estructura costa de 2 argumentos: la expresión y el elemento que deseamos recorrer.

filter(funcion, elemento_iterable)

Busquemos todos los superheroes de DC.

superheroesDC = filter(lambda superheroe: superheroe['editorial'] == "DC Comics", superheroes)

print(tuple(superheroesDC))

Como resultado tendremos.

({
  'nombre': 'Batman',
  'editorial': 'DC Comics',
  'alter_ego': 'Bruce Wayne',
  'primera_aparicion':
  'Detective Comics #27'
},{
  'nombre': 'Superman',
  'editorial': 'DC Comics',
  'alter_ego': 'Kal-El',
  'primera_aparicion': 'Action Comics #1'
})

map()

Su estructura costa de 2 argumentos: la expresión y el elemento que deseamos recorrer. En este caso se utiliza para modificar el valor de una secuencia siguiendo una regla.

map(funcion, elemento_iterable)

En el siguiente ejemplo voy a añadir a cada superheroe su poder. Como no quiero entrar en guerras innecesarias le daré un valor aleatorio entre 0 y 99.

No es recomendable usar un lambda si vas a usar más de una línea en la función.

import random

def anyadirPoder(superheroe):
    superheroe.update(poder=random.randint(0, 100))
    return superheroe

superheroesPoder = map(anyadirPoder, superheroes)

print(tuple(superheroesPoder))

El resultado sería el siguiente.

({
  'nombre': 'Batman',
  'editorial': 'DC Comics',
  'alter_ego': 'Bruce Wayne',
  'primera_aparicion': 'Detective Comics #27',
  'poder': 88
}, {
  'nombre': 'Superman',
  'editorial': 'DC Comics',
  'alter_ego': 'Kal-El',
  'primera_aparicion': 'Action Comics #1',
  'poder': 49
}, {
  'nombre': 'Spider Man',
  'editorial': 'Marvel Comics',
  'alter_ego': 'Peter Parker',
  'primera_aparicion': 'Amazing Fantasy #15',
  'poder': 35
}, {
  'nombre': 'Hulk',
  'editorial': 'Marvel Comics',
  'alter_ego': 'Bruce Banner',
  'primera_aparicion': 'The Incredible Hulk #1',
  'poder': 58
})

reduce()

Se utiliza para hacer cálculos a partir de una iteración. Su estructura costa de 3 argumentos: la expresión, el elemento que deseamos recorrer y la variable de inicio. La podemos encuentra dentro de la librería functools.

import functools

functools.reduce(funcion, elemento_iterable, variable_inicial)

Supongamos que necesitamos contar el números de superheroe que contiene el texto “man” en su nombre.

import functools

def contarMan(resultado, superheroe):
    if 'man' in superheroe['nombre'].lower():
        return resultado + 1
    else:
        return resultado

total = functools.reduce(contarMan, superheroes, 0)

print(total)
# 3

Te preguntarás, ¿por qué esta escondida? Estas funciones las podemos encontrar desde principios de 1994, pero durante el desarrollo de Python 3000 (entre 2006 y 2009), Guido van Rossum (creador de Python) pidió la eliminación de estas características por un tema de legibilidad y por existir herramientas que en conjunto realizaban las mismas tareas. Sin embargo, más tarde cambió de opinión, y solo el reduce fue eliminado a pesar de que sigue siendo accesible a través de los módulos de la librería estándar functools.

Lenguajes funcionales

No solo de Python vive el hombre. Para exprimir las posibilidades que hemos mencionado puedes dar el salto a otros lenguajes propios del paradigma.

Compañías que usan algun lenguaje funcional

La lista es grande, por lo que he realizado un filter con algunas compañías que usan Lisp o Clojure.

Extendiendo Python

Dentro de Python puedes usar la librería , la cual te aporta temas tan importantes como la concurrencia u operaciones concadenadas. También puedes usar otra librería como para implementar diccionarios inmutables.

Espero que la programación funcional deje de asustarte porque no es el lado oscuro, sino una herramienta increíblemente potente que te está esperando.

Versión escritorio