Funções de alta ordem¶
1. Introdução: Funções como Superpoderes¶
Uma das ideias mais poderosas da programação funcional em Clojure é o tratamento de funções como dados. Isso significa que uma função não é apenas um bloco de código que executa uma tarefa; ela pode ser tratada como qualquer outro valor, como um número ou uma string.
Pense nisso da seguinte forma: assim como você pode passar o número 100 para uma função que calcula um total, em Clojure, você pode passar a própria função soma como um argumento. Dizemos que as funções são "cidadãos de primeira classe", pois elas têm os mesmos direitos e privilégios que outros tipos de dados na linguagem.
Este documento explora três conceitos centrais que desbloqueiam esse superpoder e permitem escrever código mais limpo, flexível e expressivo:
- Funções de Alta Ordem (HOFs): Funções que operam sobre outras funções.
- Funções Anônimas (Lambdas): Funções "descartáveis" criadas no momento do uso. Não precisam ser associadas a um nome.
- Composição de Funções: A arte de construir novas funções combinando as existentes.
2. Funções como Cidadãos de Primeira Classe: As HOFs¶
1.1. O que significa "Cidadão de Primeira Classe"?¶
Em Clojure, quando dizemos que as funções são "cidadãos de primeira classe", queremos dizer que elas podem ser:
- Passadas como argumentos para outras funções.
- Retornadas como resultado de outras funções.
- Armazenadas em estruturas de dados ou atribuídas a um símbolo (variável) com
def, assim como qualquer outro valor.
Uma Função de Alta Ordem (HOF - High-Order Function) é simplesmente uma função que aproveita essa característica, seja recebendo outra função como argumento, seja retornando uma nova função como resultado.
Veja um exemplo prático. A função valor-descontado abaixo foi projetada para receber não apenas o valor a ser descontado, mas também a própria regra que decide se o desconto deve ser aplicado.
(defn mais-caro-que-100?
[valor-bruto]
(> valor-bruto 100))
(defn valor-descontado
"Retorna o valor com desconto de 10% se deve aplicar desconto."
[aplica? valor-bruto] ; 'aplica?' é um parâmetro que espera uma função
(if (aplica? valor-bruto)
(* valor-bruto 0.9)
valor-bruto))
;; Passando a função `mais-caro-que-100?` como argumento
(println (valor-descontado mais-caro-que-100? 1000)) ; Retorna 900.0
(println (valor-descontado mais-caro-que-100? 100)) ; Retorna 100
Neste caso, valor-descontado é uma HOF porque aceita a função mais-caro-que-100? como seu primeiro argumento, aplica?.
1.2. HOFs em Ação: , e¶
map, filter e reduce são a tríade clássica de HOFs para processamento de coleções em Clojure. Todas recebem uma função como primeiro argumento para operar sobre os elementos de uma coleção.
Para os exemplos a seguir, considere a coleção precos e uma versão de valor-descontado que aplica o desconto com base em uma regra fixa, além da função predicado aplica-desconto?:
(def precos [30 700 1000])
(defn aplica-desconto?
[valor-bruto]
(> valor-bruto 100))
(defn valor-descontado
"Retorna o valor com desconto de 10% se o valor bruto for estritamente maior que 100."
[valor-bruto]
(if (aplica-desconto? valor-bruto)
(* valor-bruto 0.9)
valor-bruto))
map: aplica uma função a cada item da coleção
A função map aplica valor-descontado a cada elemento da coleção precos.
O que aconteceu aqui:
- O preço
30permanece igual (não recebe desconto). - Os preços
700e1000são descontados em 10%.
filter: seleciona apenas os elementos que satisfazem uma condição
A função filter usa o predicado aplica-desconto? para filtrar apenas os preços elegíveis para desconto.
Resultado: Apenas os valores estritamente maiores que 100 são mantidos.
reduce: reduz a coleção a um único valor
A função reduce combina os valores da coleção em um único resultado. Exemplo: somar todos os preços.
Como funciona internamente:
- Soma os dois primeiros:
(+ 30 700)→730 - Soma o resultado com o próximo:
(+ 730 1000)→1730
O uso de HOFs é incrivelmente poderoso, mas definir uma função nomeada com defn para cada pequena operação pode ser verboso. É aqui que as funções anônimas entram em cena.
3. Funções "na Hora": O Poder das Lambdas (Funções Anônimas)¶
2.1. A Necessidade de Funções Descartáveis¶
Muitas vezes, a lógica que queremos passar para uma HOF é tão simples e usada em um único lugar que não vale a pena dar-lhe um nome formal com defn. Para esses casos, Clojure oferece as funções anônimas, também conhecidas como lambdas.
A forma fn permite criar uma função "na hora", sem associá-la a um símbolo.
;; Em vez de definir uma função `mais-caro-que-100?` separadamente,
;; criamos a mesma lógica diretamente na chamada com `fn`.
(println (valor-descontado (fn [valor-bruto] (> valor-bruto 100)) 1000))
Essa função anônima (fn [valor-bruto] (> valor-bruto 100)) existe apenas como argumento para valor-descontado e é descartada em seguida.
2.2. A Sintaxe Abreviada:¶
Para tornar as lambdas ainda mais concisas, Clojure oferece uma sintaxe abreviada #(), ideal para funções de uma única expressão. Dentro dela, os parâmetros são referenciados implicitamente:
%refere-se ao primeiro (e único) argumento.%1,%2, etc., referem-se ao primeiro, segundo e demais argumentos, caso haja mais de um.
A lógica (fn [valor-bruto] (> valor-bruto 100)) pode ser reescrita de forma muito mais compacta:
A tabela a seguir compara as três maneiras de definir a mesma lógica de verificação de preço:
| Forma | Exemplo de Código | Principal Vantagem |
|---|---|---|
defn |
(defn mais-caro-que-100? [valor-bruto] (> valor-bruto 100)) |
Reutilizável, nome claro, fácil de testar. |
fn |
(fn [valor-bruto] (> valor-bruto 100)) |
Descartável, não polui o namespace com nomes. |
#() |
#(> % 100) |
Extremamente concisa para lógicas simples. |
Versões com funções anônimas
Para operações simples, podemos usar funções anônimas com a sintaxe #():
(map #(if (> % 100) (* % 0.9) %) precos)
;=> (30 630.0 900.0)
(filter #(> % 100) precos)
;=> (700 1000)
(reduce + precos)
;=> 1730
Compondo tudo com threading (>>)¶
Você pode encadear operações de forma elegante usando o operador ->>:
Neste exemplo:
- Filtramos os preços com desconto
- Aplicamos o desconto
- Somamos o total com desconto
Resumo rápido¶
| Função | O que faz | Exemplo com precos |
|---|---|---|
map |
Transforma cada item | (map valor-descontado precos) |
filter |
Filtra itens com base em condição | (filter aplica-desconto? precos) |
reduce |
Reduz coleção a um único valor | (reduce + precos) |
A flexibilidade de criar funções rapidamente com lambdas nos leva ao próximo passo: combiná-las para criar comportamentos mais complexos.
4. Construindo Lógica com Blocos: A Composição de Funções¶
Composição de funções é o ato de combinar múltiplas funções para criar uma nova, que representa a aplicação sequencial delas. Em vez de aninhar chamadas de função, como (not (gratuito? item)), podemos usar uma HOF para construir uma função que faça isso por nós.
A principal ferramenta para composição em Clojure é a HOF comp.
3.1. Exemplo Prático: Criando a Função Oposta com comp¶
Imagine que temos uma função chamada gratuito? que verifica se um item não tem custo — ou seja, retorna true se o item for gratuito, e false caso contrário.
Agora, queremos criar a função oposta, chamada pago?, que retorna true para itens pagos e false para itens gratuitos.
Em vez de escrever toda a lógica novamente, podemos usar a função de alta ordem comp para compor a função gratuito? com a função not (que inverte um valor booleano).
Como fazer isso?
O que está acontecendo aqui?
Vamos entender passo a passo essa única linha:
comprecebe duas funções:- A função da esquerda:
not - A função da direita:
gratuito?
- A função da esquerda:
compcria uma nova função — uma função anônima que, quando chamada,- Primeiro chama a função da direita (
gratuito?) com o argumento recebido. - Depois passa o resultado dessa chamada para a função da esquerda (
not).
- Primeiro chama a função da direita (
defassocia essa nova função ao nomepago?, para que possamos reutilizá-la no nosso código.
Visualizando o fluxo
Quando chamamos:
A execução acontece assim:
Ou seja, gratuito? verifica se o item é gratuito, e not inverte o resultado, dizendo se o item é pago.
Por que isso é legal?
-
compé uma função de alta ordem:Ela recebe funções como entrada e retorna uma nova função — exatamente a definição de HOF.
-
Reutilização e clareza:
Em vez de repetir lógica, você reutiliza funções existentes e as combina para criar comportamento novo.
-
Código mais limpo e declarativo:
Você descreve o que quer fazer (compor funções) em vez de como fazer passo a passo.
Exemplo completo para testar
(defn gratuito? [item]
(= (:preco item) 0))
(def pago? (comp not gratuito?))
(pago? {:nome "Caneta" :preco 0})
;=> false
(pago? {:nome "Livro" :preco 25})
;=> true
5. Conclusão: A Tríade da Programação Funcional Expressiva¶
As Funções de Alta Ordem, as Lambdas e a Composição de Funções formam uma tríade que define a expressividade do código funcional em Clojure. Elas trabalham em perfeita sinergia:
- HOFs (
map,filter,reduce) fornecem os padrões de abstração para operar sobre dados. - Lambdas (
fn,#()) oferecem a conveniência para usar essas abstrações de forma concisa e localizada. - Composição (
comp) permite construir novas abstrações e lógicas a partir das existentes, como se fossem blocos de montar.
Dominar esses três conceitos é um passo fundamental para evoluir de apenas escrever código que funciona para escrever código Clojure que é verdadeiramente limpo, reutilizável e elegante.