1. Introdução
Programação funcional (PF) — é um paradigma de programação em que o bloco básico de construção não é o objeto nem o procedimento/método, e sim a função no sentido matemático. Na PF o foco principal é descrever "o quê calcular", e não "como calcular".
Você já se deparou com ideias individuais de PF quando trabalhou com expressões lambda e LINQ. Mas qual a diferença na prática? OOP descreve objetos e suas interações, programação procedural — um conjunto de passos, e PF — composição de funções, passar comportamento como valor, evitar mudança de estado (imutability) e ausência de efeitos colaterais.
Por que adotar esse novo paradigma?
- Código mais limpo, previsível e testável.
- Suporte a multithreading simplificado ("sem estado — sem problemas").
- Concisão e expressividade (menos código = menos bugs).
- Abstrações de alto nível, fáceis de reutilizar.
Analogia
Imagine que um restaurante recebeu um pedido: "prepare uma omelete". O cozinheiro imperativo executa uma lista de instruções: pegar ovos, quebrar, bater, fritar. O cozinheiro funcional diz: res = omelete(ovos) — ele opera com funções, abstraindo o estado da cozinha (bem, quase).
Em C# podemos usar ambos os estilos. Isso torna a linguagem bem flexível e poderosa — especialmente em projetos reais.
Conceitos-chave da PF
1. Funções de ordem superior
Funções podem ser passadas como parâmetros, retornadas de outras funções e armazenadas em variáveis. Você já fez isso com expressões lambda e delegates. Em PF essas "funções sobre funções" são a base de tudo.
2. Funções puras
Uma função é "pura" se seu resultado depende apenas dos parâmetros e ela não modifica nada fora de si (sem efeitos colaterais). Duas chamadas idênticas com os mesmos argumentos retornam o mesmo resultado.
3. Imutabilidade (Immutability)
Dados não são mutados "no lugar": o novo estado é um novo objeto. Isso facilita muito raciocinar sobre o programa e ajuda em multithreading.
4. Ausência de efeitos colaterais
A função não escreve em arquivo, não altera variáveis globais, não desenha na tela — só retorna um resultado. Na prática os efeitos colaterais são inevitáveis, mas tenta-se isolá-los nas bordas do sistema.
5. Composição de funções
Uma função pode ser construída a partir de outras, como blocos. Por exemplo: filtrar números positivos, elevar ao quadrado e somar. Cada operação é uma função separada e elas se combinam facilmente (Where → Select → Sum).
2. PF em C#: da teoria à prática
C# é uma linguagem multiparadigma: suporta bem OOP, abordagem procedural e um estilo funcional poderoso (com lambdas, delegates, extension methods e LINQ).
Vamos ver no exemplo do nosso app de estudo
Imagine que estamos desenvolvendo um programa para trabalhar com listas de números e strings. Nossa tarefa — aplicar várias operações nesses dados no estilo funcional.
Exemplo 1: Usando funções de ordem superior
// Aplica uma ação a todos os elementos da lista
public static void ForEach<T>(List<T> items, Action<T> action)
{
foreach (var item in items)
{
action(item);
}
}
Uso:
var numbers = new List<int> { 1, 2, 3, 4, 5 };
ForEach(numbers, n => Console.WriteLine(n * n)); // Função como parâmetro
Viu? Uma função pode ser "guardada" numa variável ou passada como valor — igual a passar uma maçã na cozinha!
Exemplo 2: Função pura
Função que não muda o estado do programa e depende só da entrada:
int MultiplyByTwo(int x)
{
return x * 2;
}
- Não depende de nada externo.
- Não altera nada fora dela.
- Para x = 5 sempre retorna 10.
Compare com uma função que usa e modifica uma variável global:
int total = 0;
int AddToTotal(int x)
{
total += x;
return total;
}
Isso já não é uma função pura — o resultado depende do estado externo e ela o modifica.
Exemplo 3: Imutabilidade dos dados
Em vez de modificar os dados de entrada, criamos novos:
List<int> AddOneToEach(List<int> numbers)
{
return numbers.Select(n => n + 1).ToList();
}
A lista de entrada não é modificada. Em programas multithread isso é especialmente útil: menos locks e menos race conditions.
Exemplo 4: Composição de funções
Obter a soma dos quadrados de todos os números pares:
int SumOfEvenSquares(List<int> numbers)
{
return numbers
.Where(n => n % 2 == 0) // Manter apenas os pares
.Select(n => n * n) // Elevar ao quadrado
.Sum(); // Somar
}
Legível e declarativo: cada operação é uma função separada.
3. Dicas úteis
PF, LINQ e C#
LINQ é quase "PF na prática" para coleções: você usa funções de ordem superior (Where, Select etc.), obtém novas sequências sem mutar as originais, e cada transformação é uma expressão separada. O resultado é IEnumerable<T>, que descreve o quê obter, e não como iterar.
Tabela de analogias
| Imperativo (procedural/OOP) | Funcional (LINQ/estilo PF) |
|---|---|
|
|
| Mutar a coleção | Obter uma nova coleção |
| Estado (total += x) | Funções puras (xs.Sum()) |
| Descrever como: "faça isso" | Descrever: "o que queremos obter" |
PF vs OOP: dois mundos — um só C#
Não são campos rivais. Em projetos C# reais eles se combinam: o modelo de domínio é prático de construir com classes (OOP), enquanto o processamento de coleções, agregações e transformações fica elegante em estilo funcional via LINQ, lambdas e extension methods.
Seu conhecimento sobre delegates é diretamente útil: Func<T, TResult>, Predicate<T>, Action<T> — são blocos de construção típicos do estilo PF.
Função universal de filtragem:
List<T> Filter<T>(List<T> items, Predicate<T> predicate)
{
var result = new List<T>();
foreach (var item in items)
{
if (predicate(item))
result.Add(item);
}
return result;
}
Chamadas:
var adults = Filter(people, person => person.Age >= 18);
var bigFiles = Filter(fileNames, name => name.EndsWith(".mp4") && name.Length > 10);
Em vez de muitos métodos parecidos com condições diferentes — uma função universal.
Por que empregadores e entrevistas valorizam devs com PF?
- PF ajuda a testar pequenos blocos de código sem subir todo o sistema.
- É mais simples manter a lógica: menos estados = menos fontes de bugs.
- Fica mais fácil escrever código paralelo e assíncrono — sem estado global, menos race conditions.
Como não se tornar um "fanático"?
Sim, PF é poderoso. Mas C# não é puramente funcional, e nem todas as tarefas pedem pureza absoluta. Não tenha medo de variáveis locais e mutação razoável quando fizer sentido. O importante é legibilidade, previsibilidade e testabilidade. Elementos de PF são uma ferramenta, não uma religião.
4. Erros comuns de quem começa
É muito fácil cair na ilusão de código "funcional" que na prática não é funcional.
Por exemplo, uma função retorna uma nova coleção, mas durante a criação ela muta a lista original — isso quebra o princípio da imutabilidade e surpreende quem chama a função.
Outro exemplo: uma expressão lambda acessa uma variável externa e a modifica. No paradigma funcional isso é efeito colateral e torna o comportamento menos previsível.
O compilador C# não vai te impedir: a linguagem permite ambos. Por isso, nas práticas de PF é importante garantir que a função "mora por conta própria", não modifica o exterior e não lê nada além dos seus argumentos.
GO TO FULL VERSION