Paradigma Imperativo x Declarativo x Funcional

Conceitos

Paradigma Imperativo

conceito

Estilo de programação em que você diz ao computador como fazer algo, passo a passo.

Foca em comandos, controle de fluxo (if, for, etc.) e mudança de estado em variáveis.

No imperativo, o raciocínio costuma ser mais “top down”: uma sequência de passos explícitos que o programa deve seguir.

No modelo imperativo, passo 1 → passo 2 → passo 3

Paradigma Declarativo

conceito

Estilo de programação em que você diz ao computador o que quer obter, sem detalhar o passo a passo.

Foca em descrever o resultado desejado, enquanto o “como fazer” fica escondido na linguagem, biblioteca ou framework.

No declarativo, o raciocínio costuma ser mais “por descrição de regras/expressões** (muitas vezes via funções ou expressões), dizendo o que deve ser obtido, e não o passo a passo.

Modo declarativo NEM SEMPRE é "por funções":

  • SQL: SELECT ... WHERE ...
  • HTML: <button>, <div>
  • Regras em YAML, etc.

Exemplos:

SELECT * FROM pedidos WHERE valor > 100;
<button>Salvar</button>

Paradigma Funcional

conceito

Paradigma declarativo e paradigma funcional não são a mesma coisa.

Programação funcional é um tipo de programação declarativa, mas nem toda programação declarativa é funcional.

Paradigma funcional É um tipo de programação declarativa que segue algumas regras fortes

Exemplo funcional em JS:

const numeros = [1, 2, 3, 4, 5];

const pares = numeros.filter(n => n % 2 === 0);
const soma  = pares.reduce((acc, n) => acc + n, 0);

Aqui você:


Como enxergar a relação do paradigma declarativo x funcional

Pensa assim:

👉 Todo código funcional é (em essência) declarativo, mas dá pra ser declarativo sem ser funcional (SQL, HTML, etc.).

atenção

Dentro desse guarda-chuva, temos alguns estilos, por exemplo:

  • Programação funcional (Haskell, grande parte de Clojure, Elm, etc.)
  • Lógicas / Regras (Prolog, regras de engines)
  • Consultas (SQL)
  • Declaração de interface / layout (HTML, CSS)
entendimento

Então:

  • SQL é declarativo, mas não é funcional.
  • HTML é declarativo, mas não é funcional.
  • Haskell é funcional e também declarativo.
importante

Programação declarativa é o estilo em que descrevemos o que queremos que o programa faça.

Programação funcional é um tipo de programação declarativa que se baseia em funções puras, imutabilidade e composição de funções.

Todo código funcional é declarativo, mas nem todo código declarativo é funcional.


Ponto Central

ponto central

Imperativo: você descreve como fazer.

Declarativo: você descreve o que quer.

Funcional: é um tipo de paragima declarativo que segue algumas regras fortes: funções puras, imutabilidade, funções como valores, pouco ou nenhum controle de fluxo explícito.

Paradigma Foco principal Como se pensa o código Exemplos típicos
Imperativo Como fazer (passo a passo) Sequência de comandos, loops, if, variáveis C, Go, Java, JS “na mão” com for, if, atualizando estado
Declarativo O que eu quero (resultado desejado) Descrição de regras/consultas/estrutura SQL, HTML, CSS, React JSX, ORMs com query em alto nível
Funcional O que eu quero, via funções puras Composição de funções, map, filter, reduce, imutabilidade Haskell, Clojure, Elm, partes funcionais de JS/TS (Ramda, etc.)

Qual abordagem é melhor?

A melhor abordagem é aquela em que olhando o código você entende a intenção sem esforço.

atenção !!!

👉 Regra prática que eu uso:

  • Prefira declarativo até o ponto em que continua óbvio.
  • Passou desse ponto, volte pro imperativo explícito.

Exemplo Prático

Paradigma Imperativo

linguagens
  • Exemplos de linguagens (no uso comum):
  • C, Java, Go, JavaScript (quando você escreve loops, if, for, etc.)
type ItemPedido struct {
    ProdutoID int64
    Qtd       int
}

type Estoque struct {
    ProdutoID int64
    QtdAtual  int
}

type MapaEstoque map[int64]int

// AtualizarEstoqueImperativo baixa o estoque diretamente a partir dos itens do pedido.
func AtualizarEstoqueImperativo(estoque MapaEstoque, itens []ItemPedido) error {
    // 1. Valida e atualiza tudo num loop só
    for _, item := range itens {
        qtdAtual, ok := estoque[item.ProdutoID]
        if !ok {
            return fmt.Errorf("produto %d não encontrado no estoque", item.ProdutoID)
        }

        if item.Qtd <= 0 {
            return fmt.Errorf("quantidade inválida para produto %d: %d", item.ProdutoID, item.Qtd)
        }

        if qtdAtual < item.Qtd {
            return fmt.Errorf("estoque insuficiente para produto %d: atual=%d, solicitado=%d",
                item.ProdutoID, qtdAtual, item.Qtd)
        }

        // 2. Atualiza o estoque
        novo := qtdAtual - item.Qtd
        estoque[item.ProdutoID] = novo
    }

    return nil
}

Paradigma Declarativo

Você diz o que quer, não como fazer.

linguagens
  • Exemplos de linguagens/tecnologias em estilo declarativo:

  • SQL (SELECT ... WHERE ...)

  • HTML (<div>, <button>, etc. descrevendo a tela)

  • Programação funcional (map/filter/reduce, React com JSX em boa parte)

Exemplo em SQL (declarativo):
SELECT nome
FROM clientes
WHERE ativo = 'S';

declarativo

Você não diz “faça um loop na tabela clientes…”, só declara:
“quero os nomes dos clientes onde ativo = 'S'”.

No modo DECLARATIVO montamos as funções, conforme abaixo:


// MovimentoEstoque representa um ajuste no estoque.
// Delta negativo = saída, positivo = entrada.
type MovimentoEstoque struct {
    ProdutoID int64
    Delta     int
    
    
// GerarMovimentosSaida: "para cada item do pedido, gere um movimento de saída".
func GerarMovimentosSaida(itens []ItemPedido) []MovimentoEstoque {
    movimentos := make([]MovimentoEstoque, 0, len(itens))

    for _, item := range itens {
        if item.Qtd <= 0 {
            continue // regra simples: ignora item com qtd inválida
        }
        movimentos = append(movimentos, MovimentoEstoque{
            ProdutoID: item.ProdutoID,
            Delta:     -item.Qtd, // saída => negativo
        })
    }

    return movimentos
}

// ConsolidarMovimentos agrupa por produto somando os deltas.
func ConsolidarMovimentos(movs []MovimentoEstoque) []MovimentoEstoque {
    acc := make(map[int64]int)

    for _, m := range movs {
        acc[m.ProdutoID] += m.Delta
    }

    result := make([]MovimentoEstoque, 0, len(acc))
    for produtoID, delta := range acc {
        result = append(result, MovimentoEstoque{
            ProdutoID: produtoID,
            Delta:     delta,
        })
    }

    return result
}

// ValidarEstoqueSuficiente verifica se todos os movimentos podem ser aplicados.
func ValidarEstoqueSuficiente(estoque MapaEstoque, movimentos []MovimentoEstoque) error {
    for _, m := range movimentos {
        qtdAtual, ok := estoque[m.ProdutoID]
        if !ok {
            return fmt.Errorf("produto %d não encontrado no estoque", m.ProdutoID)
        }

        novo := qtdAtual + m.Delta // lembre: Delta pode ser negativo
        if novo < 0 {
            return fmt.Errorf("estoque insuficiente para produto %d: atual=%d, movimento=%d, resultaria em %d",
                m.ProdutoID, qtdAtual, m.Delta, novo)
        }
    }
    return nil
}

// AplicarMovimentos aplica os deltas no mapa de estoque.
func AplicarMovimentos(estoque MapaEstoque, movimentos []MovimentoEstoque) {
    for _, m := range movimentos {
        estoque[m.ProdutoID] += m.Delta
    }
}

    
}```

Código Principal no estilo mais declarativo:
```go
// AtualizarEstoqueDeclarativo orquestra o fluxo de domínio.
func AtualizarEstoqueDeclarativo(estoque MapaEstoque, itens []ItemPedido) error {
    // 1. Declara: "quero movimentos de saída a partir dos itens"
    movimentos := GerarMovimentosSaida(itens)

    // 2. Declara: "quero consolidar esses movimentos por produto"
    movimentos = ConsolidarMovimentos(movimentos)

    // 3. Declara: "verifique se o estoque suporta esses movimentos"
    if err := ValidarEstoqueSuficiente(estoque, movimentos); err != nil {
        return err
    }

    // 4. Declara: "aplique os movimentos"
    AplicarMovimentos(estoque, movimentos)

    return nil
}
repare na leitura

movimentos := GerarMovimentosSaida(itens)
movimentos = ConsolidarMovimentos(movimentos)
if err := ValidarEstoqueSuficiente(estoque, movimentos); err != nil { ... }
AplicarMovimentos(estoque, movimentos)

entendimento

Isso é quase “português” de regra de negócio:

“Gera movimentos de saída, consolida, valida, aplica.”

modo declativo de ser

Os for, map, append e demais detalhes estão escondidos em helpers bem nomeados.

Como ficaria numa camada de serviço + repositório (DB real)

Imagina que em vez de MapaEstoque, você tem um repositório com Go + sqlx:

type EstoqueRepository interface {
    // Busca o estoque atual de uma lista de produtos
    BuscarPorProdutos(ctx context.Context, produtoIDs []int64) (MapaEstoque, error)

    // Aplica uma lista de movimentos (pode ser um UPDATE por produto, ou um batch)
    AplicarMovimentos(ctx context.Context, movimentos []MovimentoEstoque) error
}

Na camada de serviço, você poderia fazer algo assim (bem declarativo):

func (s *EstoqueService) BaixarEstoquePedido(ctx context.Context, itens []ItemPedido) error {
    produtoIDs := extrairProdutoIDs(itens)

    estoqueAtual, err := s.repo.BuscarPorProdutos(ctx, produtoIDs)
    if err != nil {
        return err
    }

    movimentos := GerarMovimentosSaida(itens)
    movimentos = ConsolidarMovimentos(movimentos)

    if err := ValidarEstoqueSuficiente(estoqueAtual, movimentos); err != nil {
        return err
    }

    // Aqui dá pra envolver tudo em transação, etc.
    if err := s.repo.AplicarMovimentos(ctx, movimentos); err != nil {
        return err
    }

    return nil
}

Aqui a camada de serviço está bem declarativa, focada na regra de negócio,
e o repositório por baixo usa Go imperativo normal com SQL.


O que você ganha com esse estilo “misto”

atenção

amanhã você quer suportar também movimentos de entrada?

Cria GerarMovimentosEntrada reaproveitando ConsolidarMovimentos, ValidarEstoqueSuficiente (talvez renomeie) e AplicarMovimentos.

quer logar todos os movimentos? Você mexe numa função só.

Exemplo Prático Simplificado

Versão Imperativa

func BaixarEstoqueImperativo(estoque map[int]int, pedido Pedido) error {
    // 1) Verifica se o produto existe no estoque
    qtdAtual, existe := estoque[pedido.ProdutoID]
    if !existe {
        return fmt.Errorf("produto %d não encontrado", pedido.ProdutoID)
    }

    // 2) Verifica se a quantidade é válida
    if pedido.Quantidade <= 0 {
        return fmt.Errorf("quantidade inválida: %d", pedido.Quantidade)
    }

    // 3) Verifica se tem estoque suficiente
    if qtdAtual < pedido.Quantidade {
        return fmt.Errorf("estoque insuficiente: atual=%d, solicitado=%d",
            qtdAtual, pedido.Quantidade)
    }

    // 4) Atualiza o estoque (baixa)
    estoque[pedido.ProdutoID] = qtdAtual - pedido.Quantidade

    return nil
}

Uso no main:

func main() {
    estoque := map[int]int{
        10: 100,
    }

    pedido := Pedido{
        ProdutoID:  10,
        Quantidade: 3,
    }

    err := BaixarEstoqueImperativo(estoque, pedido)
    if err != nil {
        fmt.Println("Erro:", err)
        return
    }

    fmt.Println("Novo estoque do produto 10:", estoque[10]) // 97
}
como se lê isso?

Linha por linha:
“pega quantidade… testa… testa… atualiza…”.

Isso é bem imperativo: foco total no passo a passo.


Versão Declarativa

Quebramos a mesma rotina em funções com nomes claros, ou seja, funções pequenas bem nomeadas.

função que só valida

ValidarBaixaEstoque

  func ValidarBaixaEstoque(estoque map[int]int, pedido Pedido) error {
    qtdAtual, existe := estoque[pedido.ProdutoID]
    if !existe {
        return fmt.Errorf("produto %d não encontrado", pedido.ProdutoID)
    }

    if pedido.Quantidade <= 0 {
        return fmt.Errorf("quantidade inválida: %d", pedido.Quantidade)
    }

    if qtdAtual < pedido.Quantidade {
        return fmt.Errorf("estoque insuficiente: atual=%d, solicitada=%d",
            qtdAtual, pedido.Quantidade)
    }

    return nil
}

função que só aplica a baixa

AplicarBaixaEstoque

func AplicarBaixaEstoque(estoque map[int]int, pedido Pedido) {
    estoque[pedido.ProdutoID] = estoque[pedido.ProdutoID] - pedido.Quantidade
}
função principal

BaixarEstoqueDeclarativo

func BaixarEstoqueDeclarativo(estoque map[int]int, pedido Pedido) error {
    // 1) Declara a intenção: "validar se posso baixar"
    if err := ValidarBaixaEstoque(estoque, pedido); err != nil {
        return err
    }

    // 2) Declara a intenção: "aplicar a baixa"
    AplicarBaixaEstoque(estoque, pedido)

    return nil
}

Uso no main (igual ao outro, só trocando o nome):

func main() {
    estoque := map[int]int{
        10: 100,
    }

    pedido := Pedido{
        ProdutoID:  10,
        Quantidade: 3,
    }

    err := BaixarEstoqueDeclarativo(estoque, pedido)
    if err != nil {
        fmt.Println("Erro:", err)
        return
    }

    fmt.Println("Novo estoque do produto 10:", estoque[10]) // 97
}
onde está a diferença ?

Imperativo (primeira versão)

  • A função faz tudo:

    • valida produto
    • valida quantidade
    • valida estoque
    • atualiza o map
  • É fácil de entender, mas a regra de negócio fica num “blocão”.

Declarativo (primeira versão)

A função principal (BaixarEstoqueDeclarativo) parece uma frase:

if err := ValidarBaixaEstoque(...); err != nil { ... }
AplicarBaixaEstoque(...)

  • Se lê assim:
    1. Primeiro valida se pode baixar;
    2. Depois aplica a baixa.

O "como" cada coisa é feita está escondido em funções pequenas e com nome claro.


Conclusão Final

Resumo

  • No código imperativo, você está dirigindo o carro: troca marcha, pisa no freio, vira o volante.

  • No código mais declarativo, você diz:
    verifica se pode baixar” e “baixa o estoque”.
    Os detalhes de como isso é feito moram em funções separadas.

autor

Arlei F. Farnetani Junior
farnetani@gmail.com