Módulo 10: Paradigmas & Padrões • Bloco 1 • Demonstrações Práticas

🔄 Fio Condutor - Fase 3 (ver Exemplos do Cap 01 e Exemplos do Cap 02): O Carrinho de Compras implementado sob o rigor do paradigma funcional. Aqui, nenhuma variável sofre reatribuição, os dados são registros congelados (frozen), os cálculos fluem através de funções puras de alta ordem (map, filter, reduce), e o tratamento de falhas adota tipos monádicos sem lançamento de exceções.


1. Carrinho de Compras Imutável com Composição e HOFs

Neste exemplo, o carrinho de compras é composto por estruturas 100% imutáveis (NamedTuple). O pipeline de liquidação utiliza composição funcional pura (pipe), currying e funções de alta ordem, garantindo que o estado original nunca seja corrompido:

# carrinho_funcional_puro.py
from typing import NamedTuple, Tuple, Callable, Any
from functools import reduce

# 1. Estruturas de Dados Puras e Imutáveis
class Item(NamedTuple):
    sku: str
    nome: str
    preco: float
    quantidade: int
    categoria: str

class Carrinho(NamedTuple):
    id_carrinho: str
    itens: Tuple[Item, ...]
    cupom: str = ""

# 2. Primitiva Funcional: Operador Pipe para Composição Algébrica (f ∘ g ∘ h)
def pipe(dado_inicial: Any, *funcoes: Callable[[Any], Any]) -> Any:
    """Aplica sequencialmente uma cadeia de funções puras a um dado inicial."""
    return reduce(lambda acc, fn: fn(acc), funcoes, dado_inicial)

# 3. Funções Puras de Transformação e Consulta
def calcular_subtotal_item(item: Item) -> float:
    """Calcula o subtotal de um único item sem efeitos colaterais."""
    return item.preco * item.quantidade

def calcular_subtotal_carrinho(carrinho: Carrinho) -> float:
    """Calcula a soma de todos os subtotais através de map e reduce puros."""
    return reduce(
        lambda acc, valor: acc + valor,
        map(calcular_subtotal_item, carrinho.itens),
        0.0
    )

def filtrar_itens_promocao(carrinho: Carrinho) -> Tuple[Item, ...]:
    """Retorna apenas itens elegíveis a desconto promocional (qtd >= 2)."""
    return tuple(filter(lambda item: item.quantidade >= 2, carrinho.itens))

# 4. Currying e Aplicação Parcial de Descontos e Fretes
def criar_aplicador_desconto(percentual: float) -> Callable[[float], float]:
    """Retorna uma função especializada que aplica um desconto percentual fixo."""
    fator = (100.0 - percentual) / 100.0
    return lambda total: total * fator

def criar_calculadora_frete(custo_base: float, limiar_frete_gratis: float) -> Callable[[float], float]:
    """Retorna uma função que calcula o frete com base no valor total do carrinho."""
    return lambda total: 0.0 if total >= limiar_frete_gratis else custo_base

def criar_aplicador_imposto(aliquota_icms: float) -> Callable[[float], float]:
    """Calcula o valor do imposto estadual sobre o faturamento."""
    return lambda total: total * (1.0 + aliquota_icms)

# 5. Execução e Demonstração de Imutabilidade
if __name__ == "__main__":
    itens_iniciais = (
        Item("SKU-1001", "Mouse Gamer 16000 DPI", 150.0, 1, "PERIFERICOS"),
        Item("SKU-1002", "Teclado Mecânico RGB", 350.0, 2, "PERIFERICOS"),
        Item("SKU-1003", "Cabo HDMI 2.1 Trançado", 45.0, 3, "CABOS"),
        Item("SKU-1004", "Mousepad Speed Extra Grande", 60.0, 1, "ACESSORIOS"),
    )
    
    carrinho_original = Carrinho("CART-BR-2026", itens_iniciais, cupom="PROMO15")
    
    # Cálculo de Subtotais com HOFs
    subtotal_bruto = calcular_subtotal_carrinho(carrinho_original)
    itens_em_promocao = filtrar_itens_promocao(carrinho_original)
    
    # Criar funções especializadas via currying
    desconto_cupom = criar_aplicador_desconto(15.0)       # 15% de desconto
    calculadora_frete = criar_calculadora_frete(45.0, 800.0) # Frete R$45; grátis acima de R$800
    imposto_estadual = criar_aplicador_imposto(0.08)       # 8% ICMS
    
    # Pipeline Funcional de Liquidação Financeira
    total_com_desconto = desconto_cupom(subtotal_bruto)
    frete_aplicado = calculadora_frete(total_com_desconto)
    valor_tributado = imposto_estadual(total_com_desconto)
    valor_final = valor_tributado + frete_aplicado
    
    print("=" * 65)
    print("🛒 RELATÓRIO DO CARRINHO FUNCIONAL (ESTADO IMUTÁVEL)")
    print("=" * 65)
    print(f"Identificador do Carrinho: {carrinho_original.id_carrinho}")
    print(f"Total de SKUs Registrados: {len(carrinho_original.itens)}")
    print(f"Subtotal Bruto (Soma Pura): R$ {subtotal_bruto:8.2f}")
    print(f"Valor com Cupom (-15%):     R$ {total_com_desconto:8.2f}")
    print(f"Custo de Frete Calculado:   R$ {frete_aplicado:8.2f}")
    print(f"ICMS Incidente (8%):        R$ {(valor_tributado - total_com_desconto):8.2f}")
    print("-" * 65)
    print(f"VALOR TOTAL LIQUIDADO:      R$ {valor_final:8.2f}")
    print("=" * 65)
    
    print("\n[PROVA DE IMUTABILIDADE]:")
    print(f"Carrinho original preservado intacto: {len(carrinho_original.itens)} itens.")
    print(f"Itens promocionais filtrados separadamente: {len(itens_em_promocao)} itens.")

2. Tratamento Monádico de Erros sem Exceções (Result Monad)

No paradigma funcional estrito, funções não lançam exceções não tratadas, pois isso introduz saltos não determinísticos no fluxo de execução (goto implícito). O tratamento de falhas é realizado via tipos algébricos monádicos como Result[T, E]:

# monada_result_carrinho.py
from typing import Generic, TypeVar, Callable, Any
from dataclasses import dataclass

T = TypeVar("T")
E = TypeVar("E")
U = TypeVar("U")

# 1. Definição do Tipo Algébrico Result (Either: Right=Ok, Left=Err)
@dataclass(frozen=True)
class Ok(Generic[T]):
    valor: T
    
    def eh_sucesso(self) -> bool:
        return True
        
    def bind(self, fn: Callable[[T], "Result[U, E]"]) -> "Result[U, E]":
        """Aplica a função ao valor interno se for Ok."""
        return fn(self.valor)

@dataclass(frozen=True)
class Err(Generic[E]):
    erro: E
    
    def eh_sucesso(self) -> bool:
        return False
        
    def bind(self, fn: Callable[[Any], Any]) -> "Err[E]":
        """Curto-circuita o pipeline mantendo o erro original."""
        return self

Result = Ok[T] | Err[E]

# 2. Regras de Negócio Puras que retornam Result
def validar_carrinho_nao_vazio(itens: list[dict]) -> Result[list[dict], str]:
    if not itens:
        return Err("O carrinho de compras está completamente vazio.")
    return Ok(itens)

def validar_limite_peso(itens: list[dict]) -> Result[list[dict], str]:
    peso_total = sum(item["peso_kg"] * item["qtd"] for item in itens)
    if peso_total > 30.0:
        return Err(f"Peso total de {peso_total:.1f}kg excede o limite dos Correios (30kg).")
    return Ok(itens)

def validar_cupom_promocional(cupom: str) -> Result[float, str]:
    cupons_validos = {"TECH10": 0.10, "BLACKFRIDAY": 0.25, "VIP": 0.30}
    if not cupom:
        return Ok(0.0) # Sem cupom, desconto zero (sucesso)
    if cupom in cupons_validos:
        return Ok(cupons_validos[cupom])
    return Err(f"Cupom '{cupom}' é inválido ou expirou.")

def calcular_total_liquidado(itens: list[dict], taxa_desconto: float) -> Result[float, str]:
    subtotal = sum(item["preco"] * item["qtd"] for item in itens)
    total_com_desconto = subtotal * (1.0 - taxa_desconto)
    return Ok(total_com_desconto)

# 3. Execução com Encadeamento Monádico (Pipelines Railway-Oriented)
if __name__ == "__main__":
    pedido_valido = [
        {"nome": "Notebook Gamer", "preco": 5500.0, "qtd": 1, "peso_kg": 2.8},
        {"nome": "Monitor 27 Pol", "preco": 1200.0, "qtd": 1, "peso_kg": 6.5},
    ]
    
    pedido_pesado = [
        {"nome": "Cadeira Ergonômica", "preco": 1800.0, "qtd": 2, "peso_kg": 18.0}, # Total = 36kg!
    ]

    print("=== Pipeline 1: Pedido Válido com Cupom Válido ===")
    res1 = (
        validar_carrinho_nao_vazio(pedido_valido)
        .bind(validar_limite_peso)
        .bind(lambda itens: validar_cupom_promocional("TECH10")
            .bind(lambda desc: calcular_total_liquidado(itens, desc))
        )
    )
    match res1:
        case Ok(valor):
            print(f"  ✅ Checkout Concluído com Sucesso! Total a pagar: R$ {valor:.2f}")
        case Err(msg):
            print(f"  ❌ Falha no Checkout: {msg}")

    print("\n=== Pipeline 2: Pedido com Cupom Inválido ===")
    res2 = (
        validar_carrinho_nao_vazio(pedido_valido)
        .bind(validar_limite_peso)
        .bind(lambda itens: validar_cupom_promocional("CUPOM_FALSO_2026")
            .bind(lambda desc: calcular_total_liquidado(itens, desc))
        )
    )
    match res2:
        case Ok(valor):
            print(f"  ✅ Sucesso: R$ {valor:.2f}")
        case Err(msg):
            print(f"  ❌ Falha interceptada pelo Monad: {msg}")

    print("\n=== Pipeline 3: Pedido com Violação de Peso Máximo ===")
    res3 = (
        validar_carrinho_nao_vazio(pedido_pesado)
        .bind(validar_limite_peso)
        .bind(lambda itens: validar_cupom_promocional("VIP")
            .bind(lambda desc: calcular_total_liquidado(itens, desc))
        )
    )
    match res3:
        case Ok(valor):
            print(f"  ✅ Sucesso: R$ {valor:.2f}")
        case Err(msg):
            print(f"  ❌ Falha interceptada pelo Monad: {msg}")

Saída Esperada dos Scripts:

=================================================================
🛒 RELATÓRIO DO CARRINHO FUNCIONAL (ESTADO IMUTÁVEL)
=================================================================
Identificador do Carrinho: CART-BR-2026
Total de SKUs Registrados: 4
Subtotal Bruto (Soma Pura): R$  1045.00
Valor com Cupom (-15%):     R$   888.25
Custo de Frete Calculado:   R$     0.00
ICMS Incidente (8%):        R$    71.06
-----------------------------------------------------------------
VALOR TOTAL LIQUIDADO:      R$   959.31
=================================================================

[PROVA DE IMUTABILIDADE]:
Carrinho original preservado intacto: 4 itens.
Itens promocionais filtrados separadamente: 2 itens.

=== Pipeline 1: Pedido Válido com Cupom Válido ===
  ✅ Checkout Concluído com Sucesso! Total a pagar: R$ 6030.00

=== Pipeline 2: Pedido com Cupom Inválido ===
  ❌ Falha interceptada pelo Monad: Cupom 'CUPOM_FALSO_2026' é inválido ou expirou.

=== Pipeline 3: Pedido com Violação de Peso Máximo ===
  ❌ Falha interceptada pelo Monad: Peso total de 36.0kg excede o limite dos Correios (30kg).