O paradigma declarativo

Resumo em uma linha

No paradigma declarativo você descreve o resultado que quer, e deixa o motor decidir como chegar lá — sumindo o controle de fluxo, aparece a intenção.

Pega um táxi. Você diz ao motorista: “me leva pra Rua das Acácias, 120”. Você não fala “vire à esquerda na próxima, siga 300 metros, pegue a terceira à direita”. Você declara o destino. O motorista — que conhece a cidade melhor que você — decide a rota.

Isso é o paradigma declarativo em uma frase. Você diz o quê. O motor decide o como.

No [[02 - O paradigma imperativo]], você é o navegador GPS dando cada conversão. Aqui, você é o passageiro que só fala o endereço. Dois jeitos de chegar no mesmo lugar — mas com filosofias opostas sobre quem está no controle.

Onde estamos

Esta nota é parte da trilha Paradigmas de Programação e fecha o par de raiz do domínio: depois de entender 01 - O que é um paradigma de programação e o imperativo, chegamos no seu grande oposto conceitual.

A essência: descreva o resultado, esqueça o caminho

A definição canônica é direta. O paradigma declarativo expressa a lógica de uma computação sem descrever seu fluxo de controle — ele diz o que o programa deve fazer, não como fazer.

Compare as duas frases:

  • Imperativo: “crie uma lista vazia; percorra cada número; se for par, adicione na lista; ao final, retorne a lista.”
  • Declarativo: “quero os números pares desta lista.”

Repara no que desapareceu na versão declarativa: o acumulador vazio, o laço, o if, o “adicione”, o “retorne”. Todo o andaime de controle evaporou. Sobrou só a intenção.

Para onde foi o andaime? Não sumiu — mudou de dono. Alguém ainda precisa criar a lista, iterar, testar a paridade. Esse alguém agora é o motor: o runtime, a biblioteca, o compilador, o planejador de consultas. Você terceirizou o “como”.

A pergunta que separa os dois mundos

Olhe para um trecho de código e pergunte: se eu apagar isto, o computador ainda sabe o que eu quero? No imperativo, cada linha é uma ordem indispensável — apagar uma quebra tudo. No declarativo, você descreveu um estado-alvo; o caminho é detalhe do motor.

Vamos ver esse “o quê → como” como um fluxo. Você entrega uma descrição; o motor produz a execução.

flowchart LR
    A["Você: descreve<br/>O QUE quer<br/>(a intenção)"] --> B{"O motor<br/>(runtime / planner)"}
    B --> C["Decide O COMO:<br/>algoritmo, ordem,<br/>estruturas de dados"]
    C --> D["Resultado"]
    style A fill:#2d4a3e,color:#fff
    style B fill:#3d3a5c,color:#fff
    style D fill:#4a3a2d,color:#fff

Leitura do diagrama: você só ocupa a caixa verde — a declaração da intenção. Tudo de roxo pra dentro (a escolha do algoritmo, da ordem, das estruturas) é responsabilidade do motor. A fronteira entre verde e roxo é exatamente a fronteira entre os dois paradigmas: o que você controla × o que você delega.

Não é um paradigma, é um guarda-chuva

Aqui mora a confusão mais comum. “Declarativo” não nomeia uma linguagem ou um estilo único. É um guarda-chuva — uma família de abordagens que compartilham a mesma filosofia (“descreva o resultado”) mas a aplicam em terrenos muito diferentes.

Debaixo desse guarda-chuva cabem:

  • Programação funcional — você compõe funções e transformações sobre dados, sem mutar estado nem escrever laços. Detalhada em [[05 - O paradigma funcional]].
  • Programação lógica — você declara fatos e regras; o motor de inferência deduz as respostas. Prolog é o exemplo clássico. Veja [[11 - O paradigma lógico]].
  • DSLs declarativas — linguagens de domínio específico onde você descreve um domínio, não um algoritmo:
    • SQL — você diz quais linhas quer (SELECT ... WHERE ...); o query planner do [[Banco de Dados]] decide o plano de execução: que índice usar, em que ordem juntar as tabelas, se varre ou busca.
    • HTML/CSS — você descreve a estrutura e o estilo da página; o navegador decide como pintar cada pixel, em que ordem renderizar, como reflowar quando a janela muda.
    • Expressões regulares — você descreve o padrão de texto que procura; o motor de regex constrói o autômato e faz o matching.
    • Build tools — Make, Gradle declarativo: você descreve alvos e dependências; a ferramenta decide a ordem de compilação e o que pode rodar em paralelo.
    • Infraestrutura como código — Terraform: você descreve o estado desejado da infra (“quero 3 servidores, este banco, esta rede”); o motor calcula o diff e aplica as mudanças.
    • React — você escreve a UI como função do estado (UI = f(state)); o reconciliador decide quais nós do DOM mudar.

O fio que costura tudo

O que SQL, CSS, regex, Terraform e Prolog têm em comum? Em nenhum deles você escreve um laço for. Em nenhum você diz “primeiro faça isto, depois aquilo”. Você descreve uma forma final — e confia que algo vai materializá-la.

Vamos mapear o guarda-chuva inteiro.

flowchart TD
    D["PARADIGMA<br/>DECLARATIVO<br/>(descreva o resultado)"]
    D --> F["Funcional<br/>composição de funções"]
    D --> L["Lógico<br/>fatos e regras"]
    D --> DSL["DSLs declarativas"]
    DSL --> SQL["SQL<br/>quais linhas?"]
    DSL --> WEB["HTML / CSS<br/>qual UI?"]
    DSL --> RE["Regex<br/>qual padrão?"]
    DSL --> IAC["Terraform<br/>qual estado da infra?"]
    DSL --> RX["React<br/>UI = f(estado)"]
    style D fill:#3d3a5c,color:#fff
    style DSL fill:#2d4a3e,color:#fff

Leitura do diagrama: no topo, a filosofia única (“descreva o resultado”). Embaixo, os três grandes ramos — funcional, lógico e o ramo das DSLs. O ramo DSL se abre numa árvore de domínios concretos. Note que cada folha responde uma pergunta de “qual”, nunca de “como”: qual linha, qual UI, qual padrão, qual estado. Essa repetição do “qual” é a assinatura do paradigma.

Quem decide o como? O motor

Se você abandona o controle de fluxo, alguém precisa pegá-lo. Esse alguém é o motor — e ele tem nomes diferentes em cada domínio:

DomínioVocê declaraO motor que decide o como
SQLquais linhas querquery planner / optimizer do banco
HTML/CSSa estrutura e o estiloo motor de renderização do navegador
Regexo padrão de textoo motor de regex (NFA/DFA)
Terraformo estado desejado da infrao engine de plan/apply
Reacta UI como função do estadoo reconciliador (reconciler)
Funcionala transformação dos dadoso runtime/compilador da linguagem

O deal é sempre o mesmo: você abre mão do controle fino em troca de expressividade. O query planner de um banco maduro quase sempre escreve um plano de busca melhor do que o que você escreveria à mão — porque ele conhece estatísticas das tabelas, tamanhos, índices disponíveis. Você ganha não tendo que saber disso.

O mesmo poder, escondido

Quando você escreve SELECT nome FROM usuarios WHERE idade > 18 ORDER BY nome, o banco pode: usar um índice em idade, ou varrer a tabela inteira, ou ordenar em memória, ou usar um índice já ordenado por nome. Você não escolhe nada disso. E na maioria dos casos, é ótimo que você não escolha — o motor escolhe melhor.

O mesmo problema, dois mundos

Nada deixa a diferença mais nítida do que resolver o mesmo problema dos dois jeitos. Tarefa: pegar uma lista de números, filtrar os pares e dobrar cada um.

Imperativo (você dirige cada passo):

resultado = []
for n in numeros:
    if n % 2 == 0:
        resultado.append(n * 2)
return resultado

Declarativo (você descreve a transformação):

return [n * 2 for n in numeros if n % 2 == 0]

Ou, num pipeline funcional:

return list(map(lambda n: n * 2, filter(lambda n: n % 2 == 0, numeros)))

A versão imperativa tem um acumulador mutável (resultado), um laço explícito e um append. Você é responsável por inicializar a lista, iterar, testar, anexar. Três oportunidades de bug de controle: esquecer de inicializar, errar a condição do laço, anexar no lugar errado.

A versão declarativa não tem laço, não tem acumulador, não tem mutação. Você descreveu o que a coleção de saída É: “cada n dobrado, para os n pares”. O como — alocar, iterar, anexar — é do runtime.

Agora o mesmo contraste em dados tabulares. Tarefa: somar os salários do departamento de engenharia.

Imperativo (varrer manualmente):

total = 0
for func in funcionarios:
    if func.departamento == "Engenharia":
        total += func.salario

Declarativo (SQL):

SELECT SUM(salario) FROM funcionarios WHERE departamento = 'Engenharia';

No SQL você nem menciona “percorra”, “acumule”, “some um a um”. Você diz: quero a soma dos salários onde o departamento é Engenharia. Como o banco vai chegar nesse número — varredura, índice, paralelismo — não é problema seu.

Vamos ver os dois caminhos lado a lado.

flowchart TD
    P["Problema:<br/>filtrar pares e dobrar"]
    P --> I["IMPERATIVO"]
    P --> D["DECLARATIVO"]
    I --> I1["lista vazia"]
    I1 --> I2["laço sobre cada número"]
    I2 --> I3["if par: dobra e anexa"]
    I3 --> I4["retorna a lista"]
    D --> D1["descreve:<br/>'pares, cada um dobrado'"]
    D1 --> D2["motor itera/filtra/transforma"]
    D2 --> D3["resultado"]
    style I fill:#5c3a3a,color:#fff
    style D fill:#2d4a3e,color:#fff

Leitura do diagrama: a coluna vermelha (imperativa) tem quatro caixas — quatro decisões que você toma e mantém. A coluna verde (declarativa) tem três, mas só a primeira é sua: a descrição. As outras duas (“o motor itera” e “resultado”) rodam por baixo, fora do seu campo de visão. Menos caixas suas = menos lugares pra errar, mas também menos visibilidade do que acontece.

Os trade-offs: o que você ganha e o que você perde

Declarativo não é “melhor”. É uma troca. Vale entender os dois lados.

O que você ganha

  • Concisão — menos código, e código que fala da intenção, não da mecânica.
  • Menos bugs de controle — sem laço manual, sem acumulador mutável, sem off-by-one. Bugs de “como” simplesmente não têm onde morar.
  • Otimizável pelo motor — o query planner reescreve sua consulta, o compilador funcional faz fusion de transformações. Você ganha de graça.
  • Mais fácil de raciocinar sobre o QUÊ — você lê a intenção, não disseca a mecânica.

O que você perde

  • Controle fino — quando você precisa daquele algoritmo específico, daquela ordem exata, o declarativo atrapalha.
  • Performance opaca — você não vê o como, então não vê por que está lento. Uma query SQL inocente pode varrer a tabela inteira sem você perceber.
  • Curva de entender o motor — pra usar bem SQL ou Terraform, você acaba tendo que aprender como o motor pensa. A abstração nunca é grátis.
  • Vazamento — quando o motor não faz o que você esperava, você cai num poço escuro.

Esse último ponto merece destaque: é o fenômeno da abstração que vaza. O paradigma declarativo é uma abstração — ele esconde o “como”. Mas toda abstração não-trivial vaza: em algum momento, o detalhe que ela escondia volta a importar. Sua query SQL fica lenta e, de repente, você precisa entender índices, planos de execução e estatísticas — exatamente o “como” que o paradigma prometia esconder. Esse custo de quando a abstração quebra é tema de [[Complexidade de Software]].

A armadilha do "parece mágica"

O declarativo é sedutor justamente porque parece mágica: você diz o quê, e acontece. Mas mágica que você não entende é mágica que você não consegue depurar. O bom uso do declarativo não é ignorar o motor — é confiar nele sabendo o suficiente pra desconfiar quando ele falha.

Declaratividade é um espectro, não um interruptor

Erro comum de iniciante: tratar “imperativo × declarativo” como um botão liga/desliga, como se cada linguagem ou programa fosse 100% um ou 100% o outro. Não é. É um espectro.

A maioria do código real mistura os dois. O caso mais comum do mundo:

# IMPERATIVO por fora...
conexao = abrir_banco()
for linha in conexao.executar(
    "SELECT nome, salario FROM funcionarios WHERE ativo = true"  # ...DECLARATIVO por dentro
):
    print(linha.nome, linha.salario)

Aqui há SQL declarativo (a string da consulta) embutido num laço imperativo (o for que percorre o resultado). Os dois paradigmas convivem na mesma função, na mesma respiração. Isso não é exceção — é o normal.

flowchart LR
    I["100%<br/>Imperativo<br/>(Assembly, C clássico)"] --> M["Híbrido<br/>(Python, JS, Java:<br/>laços + SQL/regex/streams)"] --> D["100%<br/>Declarativo<br/>(SQL puro, HTML, Prolog)"]
    style I fill:#5c3a3a,color:#fff
    style M fill:#3d3a5c,color:#fff
    style D fill:#2d4a3e,color:#fff

Leitura do diagrama: nas pontas, os extremos puros — Assembly de um lado, SQL/HTML do outro. O meio, onde vive a maior parte do código profissional, é híbrido: linguagens como Python, JavaScript e Java te deixam escrever laços imperativos e pipelines declarativos, embutir SQL, usar regex. Você não escolhe um lado da régua — você desliza ao longo dela conforme o problema. Esse deslizar é o tema de [[14 - Linguagens multi-paradigma]].

O senso prático

A pergunta certa não é “este código é declarativo?“. É: “neste trecho específico, eu quero descrever o resultado e confiar no motor, ou preciso dirigir cada passo?“. Filtrar uma lista? Declarativo. Implementar um algoritmo de criptografia com timing constante? Imperativo, com controle total.

O paradigma declarativo também é o solo de onde brotam ideias como a programação reativa — onde você declara relações entre dados e o sistema propaga mudanças sozinho. Esse desdobramento aparece em [[12 - Programação reativa e dataflow]].

Em entrevista

Declarative programming means you describe what result you want and let the engine decide how to produce it — the explicit control flow disappears and the intent stays. It is not a single paradigm but an umbrella covering functional, logic, and declarative DSLs like SQL, HTML/CSS, regex, and Terraform. The classic example is SQL: you specify which rows you want, and the query planner chooses the execution plan. The main trade-off is expressiveness and fewer control-flow bugs in exchange for less fine-grained control and opaque performance — you can’t see the “how”, so you can’t easily see why it’s slow. I’d also stress that declarativeness is a spectrum, not a switch: most real code mixes both, like declarative SQL embedded inside an imperative loop. A solid follow-up is the leaky abstraction point — the engine hides the “how” until it underperforms, and then you’re forced to learn the very details it was hiding.

Vocabulário

PortuguêsEnglish
paradigma declarativodeclarative paradigm
descrever o resultado / intençãodescribe the result / intent
fluxo de controlecontrol flow
o motor / runtimethe engine / runtime
planejador de consultasquery planner / optimizer
linguagem de domínio específicodomain-specific language (DSL)
estado desejadodesired state
infraestrutura como códigoinfrastructure as code (IaC)
idempotência declarativadeclarative idempotency
abstração que vazaleaky abstraction
performance opacaopaque performance

Lastro

Veja também