Programação Funcional em TypeScript: Nível Avançado
\n\nProgramação Funcional (PF) é um paradigma que trata a computação como a avaliação de funções matemáticas e evita a mudança de estado e dados mutáveis. TypeScript, sendo uma extensão do JavaScript, fornece um poderoso sistema de tipos que permite implementar muitos conceitos de PF com um novo nível de segurança e expressividade. Neste artigo, exploraremos técnicas avançadas: desde imutabilidade estrita até o trabalho com mônadas e functores.
\n\nImutabilidade e Trabalho com Estruturas Imutáveis
\n\nImutabilidade é a pedra angular da PF. Em TypeScript, podemos garanti-la no nível de tipos usando os modificadores readonly e tipos utilitários.
// Imutabilidade profunda com ReadonlyDeep\ntype DeepReadonly<T> = {\n readonly [P in keyof T]: DeepReadonly<T[P]>;\n};\n\ninterface User {\n name: string;\n address: { city: string; zip: number };\n}\n\nconst user: DeepReadonly<User> = {\n name: "Alice",\n address: { city: "Moscow", zip: 101000 }\n};\n\n// user.address.city = "SPb"; // Erro! Propriedade somente leitura\n\nPara atualizar dados com segurança, use funções puras que retornam novos objetos:
\n\nfunction updateZip(user: DeepReadonly<User>, newZip: number): DeepReadonly<User> {\n return { ...user, address: { ...user.address, zip: newZip } };\n}\n\nconst updatedUser = updateZip(user, 190000);\n\nComposição de Funções e Currying
\n\nA composição permite construir transformações complexas a partir de funções simples. Em TypeScript, com o uso de genéricos, podemos criar combinadores type-safe.
\n\n// Tipo para composição de duas funções\ntype Compose<A, B, C> = (f: (b: B) => C, g: (a: A) => B) => (a: A) => C;\n\nconst compose: Compose<any, any, any> = (f, g) => (x) => f(g(x));\n\n// Exemplo: converter string em número e incrementar em 1\nconst toNumber = (s: string): number => parseInt(s, 10);\nconst increment = (n: number): number => n + 1;\n\nconst parseAndIncrement = compose(increment, toNumber);\nconsole.log(parseAndIncrement("5")); // 6\n\nCurrying transforma uma função de vários argumentos em uma sequência de funções de um argumento. Isso torna a composição mais flexível:
\n\n// Função de currying universal com tipos\nfunction curry<T1, T2, R>(fn: (a: T1, b: T2) => R): (a: T1) => (b: T2) => R {\n return (a: T1) => (b: T2) => fn(a, b);\n}\n\nconst add = (a: number, b: number): number => a + b;\nconst curriedAdd = curry(add);\nconst add5 = curriedAdd(5);\nconsole.log(add5(3)); // 8\n\nFunctores e Mônadas: Trabalhando com Contextos
\n\nFunctor é uma estrutura de dados que suporta a operação map. Em TypeScript, um functor pode ser um array, Promise ou um tipo personalizado.
// Functor Option (Maybe) para trabalho seguro com null/undefined\ninterface Option<T> {\n map<U>(fn: (value: T) => U): Option<U>;\n getOrElse(defaultValue: T): T;\n}\n\nclass Some<T> implements Option<T> {\n constructor(private value: T) {}\n map<U>(fn: (value: T) => U): Option<U> {\n return new Some(fn(this.value));\n }\n getOrElse(_: T): T {\n return this.value;\n }\n}\n\nclass None<T> implements Option<T> {\n map<U>(_: (value: T) => U): Option<U> {\n return new None<U>();\n }\n getOrElse(defaultValue: T): T {\n return defaultValue;\n }\n}\n\n// Uso\nconst maybeNumber: Option<number> = new Some(42);\nconst result = maybeNumber.map(x => x * 2).getOrElse(0); // 84\n\nMônada estende o functor com o método flatMap (ou bind