Estágio 01 · 01-06
LockedUm paradigma é um modo de pensar sobre programa. Cada um tem assumptions diferentes sobre o que é dado, computação, e estado. Linguagens encorajam um paradigma (Haskell = funcional puro, Java = OO clássico) mas a maioria moderna é multi-paradigma (TS, JS, Python, Rust).
Não dominar paradigmas significa que:
O ponto: não escolher um paradigma como religião. Escolher por problema. Pra isso você precisa entender os 3 profundamente.
Princípio: programa é uma sequência de comandos que modificam estado. Modelo do hardware nu.
let total = 0;
for (let i = 0; i < items.length; i++) {
total += items[i].price;
}
if, while, for, goto (raramente hoje).Bom em: algoritmos com loops, manipulação de baixo nível, hot paths onde controle explícito de memória/operações importa.
Ruim em: quando você quer descrever uma transformação sem se preocupar com como (composição funcional resolve melhor), ou modelar entidades com comportamento (OO resolve melhor).
Procedural: subset de imperativo onde código é organizado em procedimentos (funções com efeito), com escopo lexical mas sem objetos. C clássico.
Princípio: programa é coleção de objetos que se comunicam por mensagens (chamadas de método). Cada objeto tem estado (atributos) e comportamento (métodos).
class Order {
constructor(private items: OrderItem[]) {}
total(): number {
return this.items.reduce((sum, i) => sum + i.subtotal(), 0);
}
addItem(item: OrderItem): void {
this.items.push(item);
}
}
4 conceitos canônicos:
private, protected, public.class B extends A, B é A. Reutiliza implementação. Cuidado: herança profunda vira pesadelo. Prefira composição.SOLID (Robert Martin):
SOLID é guia, não dogma. Aplicado mecanicamente vira boilerplate.
Composition over Inheritance: regra cardinal. class Bird extends Animal cria árvore rígida; class Bird { constructor(private fly: FlyBehavior) {} } é flexível.
Design Patterns (Gang of Four):
Conheça os nomes (vocabulário), aplique com parcimônia. Singleton é frequentemente anti-padrão (estado global disfarçado).
Críticas legítimas a OO clássico:
MathHelper.add(1, 2)).Princípio: programa é composição de funções. Funções são first-class (podem ser passadas, retornadas, armazenadas). Estado é evitado ou explicitamente isolado.
Conceitos centrais:
Mesma entrada → mesma saída. Sem efeitos colaterais (não muda variáveis fora, não faz I/O).
// pura
const add = (a: number, b: number) => a + b;
// impura (efeito colateral: muta arr)
function impureSort(arr: number[]) { arr.sort(); }
Vantagens de funções puras:
Dados não mudam. Operações retornam novos dados.
const arr = [1, 2, 3];
const novo = [...arr, 4]; // arr não muda
Vantagens: raciocínio mais simples, undo gratuito, concorrência fácil.
Custo: alocação. Estruturas persistentes (Immutable.js, immer) usam structural sharing pra mitigar.
Funções que recebem ou retornam funções.
const compose = <A, B, C>(f: (b: B) => C, g: (a: A) => B) => (a: A) => f(g(a));
map, filter, reduce são HOFs.
const add = (a: number) => (b: number) => a + b;
const add5 = add(5); // partial
add5(3); // 8
const transform = pipe(
filter(x => x > 0),
map(x => x * 2),
reduce((sum, x) => sum + x, 0),
);
Tipo que encapsula computação com estrutura. Define unit (lift valor) e bind (encadear). Exemplos:
Promise (encapsula computação assíncrona)Optional/Maybe (valor ou nada)Either/Result (sucesso ou erro)Array (zero ou mais valores)Exemplo Maybe:
type Maybe<T> = { kind: 'just'; value: T } | { kind: 'nothing' };
const map = <A, B>(m: Maybe<A>, f: (a: A) => B): Maybe<B> =>
m.kind === 'just' ? { kind: 'just', value: f(m.value) } : m;
const flatMap = <A, B>(m: Maybe<A>, f: (a: A) => Maybe<B>): Maybe<B> =>
m.kind === 'just' ? f(m.value) : m;
flatMap (também chamado bind/>>=) é a operação monádica chave. Permite encadear sem unwrapping/wrapping manual.
Computar só quando necessário. Permite estruturas infinitas (lista de todos primos), curto-circuito, performance via fusão.
function* naturals() {
let i = 0;
while (true) yield i++;
}
// generator é lazy: gera valor por valor, sob demanda
Sum types (tipo A | tipo B) e product types ({ x: A, y: B }). Combinados, modelam domínio com precisão.
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number }
| { kind: 'rectangle'; width: number; height: number };
const area = (s: Shape): number => {
switch (s.kind) {
case 'circle': return Math.PI * s.radius ** 2;
case 'square': return s.side ** 2;
case 'rectangle': return s.width * s.height;
}
// TS conhece exhaustiveness, sem default, erro se case faltar.
};
Pattern matching (em TS via switch + discriminated union; em Rust/Haskell nativamente) é o pão da programação funcional moderna.
Linguagens puras (Haskell, PureScript) proíbem efeitos sem encapsulamento monádico. Linguagens multi-paradigma (JS, TS, Scala, Rust) permitem estilo funcional sem rigor total, escolher quando vale.
Subset/extensão funcional. Programa é fluxo de eventos (Observable). Operadores transformam fluxos.
import { fromEvent } from 'rxjs';
import { map, filter, debounceTime } from 'rxjs/operators';
fromEvent(input, 'input')
.pipe(
map(e => e.target.value),
debounceTime(300),
filter(q => q.length > 2),
)
.subscribe(query => doSearch(query));
Bom pra UI, eventos, streams, jogos. RxJS, RxJava, ReactiveX são bibliotecas de referência. React Hooks tem inspiração reativa.
| Problema | Paradigma natural |
|---|---|
| Loop apertado, otimização baixa | Imperativo |
| Pipeline de transformação de dados | Funcional |
| Modelar entidades com identidade e ciclo de vida | OO (com cuidado) |
| Sistema de eventos | Reativo / FP |
| Concorrência sem locks | FP (imutável) ou actor model |
| Algoritmos clássicos (sort, search) | Imperativo (loops) ou FP (recursão) |
| Domínios complexos (DDD) | OO + FP (entidades OO; pipelines FP) |
| UI declarativa | FP (React funcional) |
Em TypeScript moderno, a regra prática:
First-class: funções podem ser:
Closure: função + ambiente léxico capturado. Em JS:
function counter() {
let count = 0; // capturada
return () => ++count; // closure sobre count
}
const c = counter();
c(); c(); c(); // 1, 2, 3
Closures explicam: módulos com estado privado, factories, callbacks com contexto. Aprofundamento em 01-07.
Pra passar o Portão Conceitual, sem consultar:
Shape.Implementar Result<T, E> (sum type) e uma pipeline de validação composicional em TypeScript.
Result<T, E> (também chamado Either):
type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
ok<T>(value: T): Result<T, never>err<E>(error: E): Result<never, E>map<T, U, E>(r: Result<T, E>, f: (t: T) => U): Result<U, E>flatMap<T, U, E>(r: Result<T, E>, f: (t: T) => Result<U, E>): Result<U, E>mapErr<T, E, F>(r: Result<T, E>, f: (e: E) => F): Result<T, F>combine<E>(...results: Result<unknown, E>[]): Result<unknown[], E[]>, agrega múltiplos, retorna lista de erros se algum falhou.Order de logística:
type Order = {
customerId: string;
items: Array<{ sku: string; qty: number; price: number }>;
deliveryAddress: { lat: number; lng: number };
deadline: Date;
};
customerId não vazio, formato UUID v4.items não vazio, cada item: qty > 0, price > 0, sku matches ^[A-Z0-9-]+$.deliveryAddress: lat ∈ [-90, 90], lng ∈ [-180, 180].deadline no futuro.Result + composição: sem throw/try/catch no caminho de validação.field, code, message), agregados (não para no primeiro).vitest/jest pra testes).(input) => Result<output, errors[]>, combinadas por combine/flatMap.Result é melhor que throw/try/catch em domínio de validação?never no return de ok/err ajuda inferência.Validation<T, E> applicative (variante do Result que acumula erros automaticamente em sequência, sem precisar de combine explícito).AsyncResult<T, E>.never é o bottom type. Conditional types simulam computação em tipos.Encerramento: após 01-06 você para de escolher paradigma por inércia. Cada decisão de design (classe vs função, mutável vs imutável, herança vs composição) vira uma escolha consciente baseada no problema.
Destrava
01-06 é prereq dos seguintes módulos: