Programação Funcional em TypeScript: da Teoria à Prática
Programação Funcional (PF) é um paradigma baseado em computação através da aplicação de funções e na evitação de mudanças de estado. TypeScript, sendo um superconjunto estritamente tipado de JavaScript, fornece ferramentas poderosas para implementar PF. Neste artigo, vamos analisar os conceitos-chave da programação funcional em TypeScript com exemplos práticos de código.
1. Funções Puras e Imutabilidade
Função Pura é uma função que:
- Sempre retorna o mesmo resultado para os mesmos argumentos (determinismo).
- Não tem efeitos colaterais (não altera variáveis externas, não realiza operações de I/O).
// Função puraconst add = (a: number, b: number): number => a + b;
// Função impura (efeito colateral — alteração de estado externo)let counter = 0;const increment = (): number => ++counter;Imutabilidade significa que os dados não são alterados, mas sim criadas novas cópias. Em TypeScript, pode-se usar o operador spread e métodos sem mutações:
interface User { name: string; age: number;}
// Mutação (ruim)const updateAgeMutable = (user: User, newAge: number): void => { user.age = newAge;};
// Abordagem imutável (boa)const updateAgeImmutable = (user: User, newAge: number): User => ({ ...user, age: newAge,});A imutabilidade simplifica a depuração e os testes, pois os dados são sempre previsíveis.
2. Funções de Alta Ordem e Closures
Funções de Alta Ordem são funções que recebem outras funções como argumentos ou as retornam. Exemplo — o método Array.map:
const numbers: number[] = [1, 2, 3, 4];const doubled: number[] = numbers.map((x) => x * 2);console.log(doubled); // [2, 4, 6, 8]Closure é uma função que "lembra" as variáveis do escopo externo mesmo após a função externa ter terminado sua execução:
const createMultiplier = (factor: number): ((x: number) => number) => { return (x: number) => x * factor;};
const double = createMultiplier(2);const triple = createMultiplier(3);
console.log(double(5)); // 10console.log(triple(5)); // 15Closures são amplamente utilizados para criar funções com aplicação parcial de argumentos (currying).
3. Composição de Funções e Currying
Composição de Funções é a combinação de várias funções em uma, onde a saída de uma função é a entrada para a próxima. Em TypeScript, pode-se escrever uma utilidade para composição:
const compose = <T, R>(...fns: Array<(arg: T) => T>) => (arg: T): T => fns.reduceRight((acc, fn) => fn(acc), arg);
// Exemplo: transformação de stringconst toUpperCase = (s: string): string => s.toUpperCase();const exclaim = (s: string): string => `${s}!`;const shout = compose(toUpperCase, exclaim);
console.log(shout("hello")); // "HELLO!"Currying é a transformação de uma função com vários argumentos em uma sequência de funções com um argumento:
// Função normalconst add = (a: number, b: number): number => a + b;
// Versão curriedconst curriedAdd = (a: number) => (b: number): number => a + b;
const add5 = curriedAdd(5);console.log(add5(3)); // 8Currying simplifica a criação de funções especializadas e aumenta a reutilização de código.
4. Mônadas e Trabalho com Option/Maybe
Mônada é um padrão de programação funcional que encapsula um valor e fornece uma interface para sua transformação. Uma das mônadas mais simples é a Maybe (ou Option), que lida com segurança com n