Programação Funcional em C#: da Sintaxe à Arquitetura
\n\nO C# moderno há muito ultrapassou os limites de uma linguagem puramente orientada a objetos. A partir da versão 3.0 (LINQ) e até os lançamentos mais recentes do .NET, a linguagem absorve ativamente os paradigmas da programação funcional (PF). Se você já domina lambdas e LINQ, mas quer se aprofundar, este artigo é para você. Vamos explorar conceitos avançados de PF que tornarão seu código C# mais previsível, testável e conciso.
\n\n1. Funções Puras e Trabalho com Efeitos Colaterais
\n\nFunção pura é uma função que:
\n- \n
- Sempre retorna o mesmo resultado para os mesmos argumentos (transparência referencial). \n
- Não possui efeitos colaterais (não altera estado global, não escreve em arquivo, não envia requisições HTTP). \n
Em C#, o exemplo ideal de função pura são operações matemáticas. O problema é que uma aplicação real não pode consistir apenas de funções puras: mais cedo ou mais tarde, é necessário salvar dados ou enviar um e-mail. A solução é separar a lógica dos efeitos.
\n\nTécnica avançada: use functors e mônadas para encapsular efeitos. Vamos considerar um exemplo simples com Maybe<T> (análogo ao nullable, mas funcional):
public class Maybe<T>\n{\n private readonly T _value;\n public bool HasValue { get; }\n \n private Maybe(T value, bool hasValue) => (_value, HasValue) = (value, hasValue);\n \n public static Maybe<T> Some(T value) => new Maybe<T>(value, true);\n public static Maybe<T> None() => new Maybe<T>(default, false);\n \n // Functor: Select (Map)\n public Maybe<TResult> Select<TResult>(Func<T, TResult> selector) =>\n HasValue ? Maybe<TResult>.Some(selector(_value)) : Maybe<TResult>.None();\n \n // Mônada: SelectMany (FlatMap/Bind)\n public Maybe<TResult> SelectMany<TResult>(Func<T, Maybe<TResult>> binder) =>\n HasValue ? binder(_value) : Maybe<TResult>.None();\n}\n\n// Uso\nMaybe<int> result = Maybe<int>.Some(10)\n .Select(x => x * 2)\n .SelectMany(x => x > 15 ? Maybe<int>.Some(x) : Maybe<int>.None());\n\nEste código não gera exceções nem verifica null manualmente — toda a lógica de tratamento de ausência de valor está encapsulada na mônada.
2. Currying e Aplicação Parcial
\n\nNa PF clássica, cada função recebe um único argumento. Currying é a transformação de uma função com vários parâmetros em uma cadeia de funções aninhadas, cada uma recebendo um argumento. Aplicação parcial é a fixação de parte dos argumentos.
\n\nEm C#, não há currying nativo, mas é fácil implementá-lo usando closures:
\n\n// Função comum\nFunc<int, int, int, int> add = (a, b, c) => a + b + c;\n\n// Versão curried\nFunc<int, Func<int, Func<int, int>>> curriedAdd = a => b => c => a + b + c;\n\n// Aplicação parcial: fixamos o primeiro argumento\nFunc<int, Func<int, int>> add5 = curriedAdd(5);\n\n// Uso\nint sum = add5(10)(20); // 35\n\nCenário avançado: injeção de dependências via aplicação parcial. Em vez de passar configuração ou serviço pelo construtor da classe, você pode fixá-los na etapa de composição:
\n\npublic class ReportService\n{\n public Func<string, string, string> FormatReport => \n (template, data) => $"[{DateTime.Now}] {template}: {data}";\n}\n\n// Uso com aplicação parcial\nvar service = new ReportService();\nvar formatWithTemplate = service.FormatReport("Error"); // fixamos o template\nstring errorMessage = formatWithTemplate("Connection failed");