Conteúdos da cadeira

Programação funcional

Paradigmas, funções puras, map, filter e reduce, lambda e funções de ordem superior.

Markdown

Perguntar sobre esta página

ChatGPTClaudePerplexityGeminiCopiar e abrir

Envia o link e pede à IA para ler a página. No Gemini, cola a pergunta copiada.

Ver pergunta para copiar
Nesta página

Há várias formas de organizar um programa, chamadas paradigmas. Os programas procedimentais são listas de instruções que dizem ao computador o que fazer, passo a passo (C e Pascal são exemplos). As linguagens declarativas descrevem o problema e deixam a linguagem descobrir como calcular (SQL é o exemplo típico). As linguagens orientadas por objetos organizam o programa em objetos com estado interno e métodos (Java e Smalltalk). Python suporta vários paradigmas sem obrigar a nenhum, o que permite escolher o mais legível para cada problema.

O paradigma funcional decompõe problemas em funções que recebem entradas e produzem saídas, idealmente como funções matemáticas: o mesmo input dá sempre o mesmo output, sem efeitos secundários (sem alterar variáveis globais, sem ler nem escrever a meio da computação). Uma função assim chama-se pura e é mais fácil de testar e de depurar, porque o seu comportamento depende apenas dos argumentos.

map, filter e reduce

Três funções capturam os padrões mais comuns de percorrer sequências. A map aplica a mesma função a todos os elementos. A filter guarda os elementos que satisfazem um predicado (uma função que devolve um lógico). A reduce, do módulo functools, combina todos os elementos num único valor usando uma função de dois argumentos.

from functools import reduce

numeros = [1, 2, 3, 4]
print(list(map(lambda x: x * 2, numeros)))
print(list(filter(lambda x: x % 2 == 0, numeros)))
print(reduce(lambda a, b: a + b, numeros))

Isto escreve [2, 4, 6, 8], [2, 4] e 10. A palavra lambda cria uma função pequena sem nome: lambda x: x * 2 é a função que dobra o seu argumento. Repara no list(...) à volta de map e filter: estas funções são preguiçosas (lazy), devolvem um iterador que só calcula os valores quando pedidos, e cada valor só pode ser consumido uma vez. Para veres o resultado como lista, converte com list.

Casos particulares do reduce tão comuns que têm nomes próprios: sum soma, any pergunta se algum elemento é verdadeiro e all pergunta se todos são. Usa-os em vez de escreveres o reduce equivalente.

Funções de ordem superior

Uma função de ordem superior trata outras funções como valores: recebe funções como argumentos ou devolve funções como resultado. map, filter e reduce são exemplos do primeiro caso. Python trata funções como objetos de primeira classe: podes guardá-las em variáveis, passá-las e devolvê-las como qualquer outro valor.

def aplicar_duas_vezes(f, x):
    return f(f(x))

def sucessor(n):
    return n + 1

print(aplicar_duas_vezes(sucessor, 5))

Isto escreve 7: sucessor aplicado a 56, e aplicado outra vez dá 7. A função aplicar_duas_vezes não sabe nada sobre sucessores; funciona com qualquer função de um argumento. Esta generalidade é o ponto das funções de ordem superior.

Duas ferramentas do módulo operator são úteis nestes contextos. A itemgetter(n) devolve uma função que extrai a posição n, e a concat concatena strings ou listas. Elas evitam escreveres lambda triviais quando só precisas de ir buscar um elemento.

Ordenar com chave

A função sorted aceita um argumento key: uma função aplicada a cada elemento para decidir a ordem, sem alterar os elementos devolvidos.

palavras = ['programação', 'em', 'python']
print(sorted(palavras))
print(sorted(palavras, key=len))
print(sorted(palavras, key=lambda s: s[-1]))

Isto escreve ['em', 'programação', 'python'], ['em', 'python', 'programação'] e ['em', 'python', 'programação']. A primeira ordena por ordem lexicográfica. A segunda ordena pelos comprimentos 2,6,112, 6, 11. A terceira ordena pelo último caráter (m, n, o). Repara que key=len passa a função len sem a chamar: é o sorted que a chama para cada elemento.

Exemplo completo: pipeline funcional

Dada uma lista de notas, queremos a média das notas de aprovação (maiores ou iguais a 10), calculada como soma a dividir pela contagem. Em estilo funcional, cada passo é uma transformação da sequência: filtrar, depois agregar.

from functools import reduce

def media_aprovados(notas):
    aprovadas = list(filter(lambda n: n >= 10, notas))
    if not aprovadas:
        return 0
    total = reduce(lambda a, b: a + b, aprovadas)
    return total / len(aprovadas)

print(media_aprovados([8, 12, 15, 7, 10]))
print(media_aprovados([5, 7]))

Isto escreve 12.333333333333334 e 0. Segue o primeiro caso: filter guarda [12, 15, 10]; o reduce soma para 37; 37 / 3 dá a média. O teste if not aprovadas protege a divisão por zero quando ninguém passa: sem ele, len(aprovadas) seria 0 e o programa morreria com erro de execução. Este teste de entrada vazia é o pormenor que distingue uma solução completa de uma incompleta.

Ver o ficheiro no GitHub

À tua maneira

Escolhe como preferes ler.

Aparência
Ajustar cores e largura
Cor de destaque do tema FEUP
Tipo de letra

Álgebra, lógica e uma ideia de cada vez.

As tuas escolhas ficam guardadas neste navegador.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.