A programação funcional é um paradigma que trata a computação como a avaliação de funções matemáticas e evita mudanças de estado e dados mutáveis. Em Python, embora não seja uma linguagem puramente funcional, oferecemos ferramentas e padrões que nos permitem adotar esse estilo, tornando o código mais previsível, testável e conciso. Nesta aula, vamos explorar os conceitos essenciais e as ferramentas que Python disponibiliza para programação funcional.

Dominar esses conceitos é fundamental para escrever código mais limpo e expressivo, especialmente quando lidamos com transformações de dados em coleções. Além disso, muitos desses conceitos são amplamente utilizados em bibliotecas e frameworks modernos, como Pandas e Spark, então vale a pena compreendê-los a fundo.

map, filter, reduce

As funções map, filter e reduce são funções de alta ordem, ou seja, recebem outras funções como argumento. Elas são fundamentais para manipular coleções de forma declarativa, sem a necessidade de laços explícitos.

map aplica uma função a cada elemento de um iterável e retorna um novo iterável com os resultados. filter seleciona elementos que satisfazem uma condição (função que retorna booleano). reduce acumula os elementos de um iterável em um único valor, aplicando uma função binária cumulativamente. Em Python 3, reduce foi movida para o módulo functools, enquanto map e filter são builtins.

# Exemplo de map: elevar ao quadrado cada número
numeros = [1, 2, 3, 4]
quadrados = list(map(lambda x: x ** 2, numeros))
print(quadrados)  # [1, 4, 9, 16]

# Exemplo de filter: selecionar apenas pares
pares = list(filter(lambda x: x % 2 == 0, numeros))
print(pares)  # [2, 4]

# Exemplo de reduce: somar todos os números
from functools import reduce
soma = reduce(lambda acc, x: acc + x, numeros, 0)
print(soma)  # 10

Observe que map e filter retornam objetos iteráveis (em Python 3, são generators), por isso usamos list() para materializar. Isso é eficiente para grandes conjuntos de dados, pois a computação é preguiçosa.

functools

O módulo functools fornece ferramentas para funções de alta ordem e operações com funções. Além de reduce, ele inclui partial (fixar argumentos), lru_cache (cache de resultados), wraps (para decoradores) e cmp_to_key (converte função de comparação em key).

Essas ferramentas aumentam a expressividade e a eficiência do código funcional. Por exemplo, partial permite criar novas funções a partir de outras, pré-preenchendo alguns argumentos, o que promove reuso e clareza.

from functools import partial, lru_cache

# partial: fixar o expoente em 2
potencia2 = partial(pow, exp=2)
print(potencia2(5))  # 25

# lru_cache: memoização para funções puras
@lru_cache(maxsize=None)
def fatorial(n):
    if n < 2:
        return 1
    return n * fatorial(n-1)

print(fatorial(10))  # 3628800
print(fatorial.cache_info())  # CacheInfo(hits=0, misses=11, maxsize=None, currsize=11)

O uso de lru_cache é uma técnica clássica de programação funcional para evitar recálculos, desde que a função seja pura (mesmo resultado para os mesmos argumentos).

Imutabilidade

Imutabilidade significa que, uma vez criado, um objeto não pode ser modificado. Em Python, tipos como tuple, str, int e frozenset são imutáveis. A imutabilidade é um pilar da programação funcional, pois evita efeitos colaterais e torna o código mais previsível.

Trabalhar com dados imutáveis facilita a concorrência e o raciocínio sobre o código, já que não há risco de alterações acidentais. Em vez de modificar listas, criamos novas listas a partir das originais, usando compreensões ou funções como map e filter.

# Exemplo de imutabilidade: usar tuplas e criar novas estruturas
original = (1, 2, 3)
novo = original + (4,)
print(original)  # (1, 2, 3)
print(novo)      # (1, 2, 3, 4)

# Em vez de modificar uma lista, criamos uma nova
lista = [1, 2, 3]
lista_mais_um = [x + 1 for x in lista]
print(lista)        # [1, 2, 3]
print(lista_mais_um) # [2, 3, 4]

Note que a imutabilidade não é obrigatória em Python, mas é uma boa prática quando se adota o estilo funcional. Podemos usar bibliotecas como types.MappingProxyType para criar dicionários somente-leitura.

Funções puras

Uma função pura é aquela que, dado o mesmo conjunto de entradas, sempre retorna o mesmo resultado e não possui efeitos colaterais (não modifica variáveis globais, não imprime, não altera argumentos, etc.). Funções puras são fáceis de testar e depurar, e são a base da programação funcional.

Em Python, podemos escrever funções puras seguindo boas práticas: evitar usar variáveis globais, não modificar argumentos mutáveis, e não realizar operações de I/O dentro delas. Isso contrasta com funções impuras, que podem depender de estado externo ou produzir efeitos colaterais.

# Função pura: não modifica nada externo
def somar(a, b):
    return a + b

# Função impura: modifica uma variável global
contador = 0
def incrementar():
    global contador
    contador += 1
    return contador

# Função impura: imprime na tela
def imprimir_soma(a, b):
    print(a + b)

Preferir funções puras facilita a composição e o reuso. Por exemplo, podemos usar map com funções puras sem medo de efeitos inesperados.

Referências

Exercícios

  1. Use map para converter uma lista de strings em uma lista de seus comprimentos. Exemplo: ['casa', 'python', 'funcao'] deve resultar em [4, 6, 7].

    ✓ Resposta:
    palavras = ['casa', 'python', 'funcao']
    comprimentos = list(map(len, palavras))
    print(comprimentos)  # [4, 6, 7]
  2. Utilizando filter, obtenha apenas os números positivos de uma lista que contém negativos, positivos e zero. Exemplo: [-1, 2, 0, -3, 5] deve resultar em [2, 5].

    ✓ Resposta:
    numeros = [-1, 2, 0, -3, 5]
    positivos = list(filter(lambda x: x > 0, numeros))
    print(positivos)  # [2, 5]
  3. Usando reduce (de functools), calcule o produto de todos os elementos de uma lista. Exemplo: [1, 2, 3, 4] deve resultar em 24.

    ✓ Resposta:
    from functools import reduce
    numeros = [1, 2, 3, 4]
    produto = reduce(lambda acc, x: acc * x, numeros, 1)
    print(produto)  # 24
  4. Crie uma função pura chamada dobrar que recebe um número e retorna o dobro. Não deve haver efeitos colaterais.

    ✓ Resposta:
    def dobrar(x):
        return x * 2
  5. Explique por que a função somar abaixo é pura, mas somar_imprimir não é: def somar(a, b): return a + b e def somar_imprimir(a, b): print(a + b).

    ✓ Resposta: A função somar é pura porque sempre retorna o mesmo resultado para os mesmos argumentos e não causa efeitos colaterais (não altera nada externo). Já somar_imprimir é impura porque imprime na tela, o que é um efeito colateral observável, além de retornar None.

Boas Práticas e Observações Finais

Ao adotar programação funcional em Python, lembre-se de que o estilo é uma ferramenta, não uma obrigação. Use map, filter e reduce quando tornarem o código mais legível; caso contrário, compreensões de lista podem ser mais claras. Prefira funções puras sempre que possível, mas não evite I/O em todo o programa — a ideia é isolá-lo em pontos específicos.

Além disso, explore o módulo itertools para operações avançadas com iteráveis, e considere bibliotecas como toolz ou fn.py para um suporte mais amplo ao paradigma funcional. A prática constante é essencial para internalizar esses conceitos.