Python funcional

Guillermo Vayá Pérez

Sobre mí

Guillermo Vayá <guivaya@gmail.com>

Twitter: @Willyfrog_

Github: Willyfrog

willyfrog.gif

Trabajo en: Sorry, your browser does not support SVG.

Conceptos básicos funcionales

  • HOF
  • Composicion
  • Efectos colaterales
  • Mutabilidad

Conceptos básicos: HOF

Funciones de Orden Superior: funciones=valor

Nos lleva a: Lambdas y Closures

from nose.tools import raises

@raises(Exception)
def test_that_fails_by_passing():
    pass

Conceptos básicos: Composición

Composicion de dos funciones

def compose_fn(f1, f2):
    return lambda x: f1(f2(x))
asegura_impar = compose_fn(
    lambda x: x +1,
    lambda x: x * 2
)

Conceptos básicos: Composición II

Y a partir de aquí, seguimos…

compose_fn(compose_fn(compose_fn(.......)))

Hasta que se caiga

stack.jpg

Conceptos básicos: Efectos colaterales

Dos ejecuciones de la misma función no generan el mismo resultado.

Tratar de acotarlo

def hola(nombre):
    escribe_a_fichero("hola {0}".format(nombre))
    return cuenta_lineas()

Conceptos básicos: Mutabilidad

En python mejor no preocuparse por ella…

… pero intentar minimizar!

Conceptos básicos: InMutabilidad

tuplas y tuplas con nombre

no son tan flexibles como la lista y el diccionario.

menor consumo de memoria

estructuras mas simples

Reduce, Map, Filter

¿Por qué usarlas?

Usamos variaciones de ellas a menudo, posiblemente imperativas (loops)

Díficil comprension inicial, pero después generan código limpio y claro

Estan presentes en casi todos los lenguajes desde hace tiempo (excepción: Go)

List comprehension

[item * 2 for item in item_list if (x % 2)]

Reduce

Agrupa los resultados de una secuencia mediante una funcion

No reproducible mediante list comprehensions

usar functools.reduce en Python3

Ejemplo

reduce(lambda acc, x: acc + [x*2] if (x % 2) else acc, range(1,5), [])

Definiendo reduce imperativamente

def reducir(funcion, secuencia, inicial=None):
    if not secuencia:
        return inicial
    acc = inicial
    while secuencia:
        valor = secuencia.pop()
        acc = funcion(acc, valor)

    return acc

Definiendo reduce recursivamente

def reducir(funcion, secuencia, inicial=None):
    if not secuencia:
        return inicial
    valor = secuencia.pop()
    return reducir(funcion, secuencia, funcion(valor, inicial))

En python no es cómodo: nos falta pattern matching

Filter

Deja una lista con los valores que nos interesan

def filtrar(filtro, secuencia):
    parcial = anade_si(filtro)
    return reducir(parcial, secuencia, [])


def anade_si(filtro):
    return lambda acc, valor: [valor] + acc if filtro(valor) else acc

Map

Recorre una lista

def mapear(funcion, secuencia):
    parcial = aplica_une(funcion)
    return reducir(parcial, secuencia, [])

def aplica_une(funcion):
    return lambda acc, valor: [funcion(valor)] + acc

Revisando la list comprehension

[map for secuencia if filter]

Ejemplo

print mapear(
    lambda x: x * 2,
    filtrar(lambda y: y % 2,
            range(1, 5))
)

¿Fácil no?

oh-my-god.jpg

Ejemplo II

x2 = lambda x: x * 2 
impar = lambda y: y % 2
print mapear(
    x2,
    filtrar(impar, range(1, 5))
)

Ejemplo III

filtra_impares = lambda r: filtrar(impar, r)
print mapear(x2, filtra_impares(range(1, 5)))

dobla_impares = lambda sec: mapear(x2, filtra_impares(sec))

print dobla_impares(range(1, 5))

No esta mal

not-bad.jpg

Recursividad

def cuenta(num):
  return cuenta(num-1) if num > 0 else "This is the end"

print cuenta(1000000)

Problema

Traceback (most recent call last): File "ejemplos.py", line 30, in <module> print cuenta(1000000) File "ejemplos.py", line 22, in cuenta return cuenta(num-1) if num > 0 else "This is the end"

…(cerca de 1000 repeticiones de la mista aburrida traza)

File "ejemplos.py", line 22, in cuenta return cuenta(num-1) if num > 0 else "This is the end" RuntimeError: maximum recursion depth exceeded

Ñapa

import sys
sys.setrecursionlimit(1000001)

TCO y Trampolining

TCO elimina el stack de la recursividad

una llamada es recursiva usando tailcall si al final de la ejecucion solo queda una llamada a la funcion

def suma(secuencia):
    """No TailCall"""
    if not secuencia:
        return 0
    x = pop(secuencia)
    return x + suma(secuencia)
def suma(secuencia):
    """TailCall"""
    return _suma(secuencia, 0)

def _suma(secuencia, acc):
    if not secuencia:
        return acc
    x = secuencia.pop()
    return _suma(secuencia, acc+x)

Trampolining

Usar un bucle que llame a la funcion que queremos hacer recursiva con un centinela para que avise de cuando dejar de invocarla

def trampolin(fun, *args, **kwargs):
    result = [fun, args, kwargs]
    while es_funcion(result):
        (fn, fun_args) = (result[0], result[1])
        if (len(result) == 3):
            fun_kwargs = result[2]
        else:
            fun_kwargs = {}
        result = fn(*fun_args, **fun_kwargs)

    return result


def es_funcion(lista):
    try:
        return callable(lista[0])
    except:
        return False

Trampolining: Ejemplo

def cuenta_t(num):
    return [cuenta_t, [num-1]] if num > 0 else "This is the end"

print trampolin(cuenta_t, 1000000)

>>> from ejemplos import *

>>> print trampolin(cuentat, 1000000)

This is the end

Buenas practicas derivadas de la PF

  • nombrar apropiadamente
  • no reutilizar variables para asignación
  • control de efectos colaterales
  • limitar funcionalidad de cada función/metodo

Otros recursos

functools

https://docs.python.org/2/library/functools.html

functools.png

fn.py

Utilidades funcionales

https://github.com/kachayev/fn.py

fnpy.png

Hy

Lisp compatible con python

http://hylang.org/

hy.png

Lenguajes funcionales

Ampliar tu forma de pensar

Gracias

¿Preguntas?

De nuevo sobre mí

Guillermo Vayá <guivaya@gmail.com>

Twitter: @Willyfrog_

Github: Willyfrog

willyfrog.gif

Trabajo en: Sorry, your browser does not support SVG.

Codigo en Github

No uses este codigo

gist.png