CodeGym /Cursos /C# SELF /Introdução à programação funcional

Introdução à programação funcional

C# SELF
Nível 51 , Lição 0
Disponível

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 (WhereSelectSum).

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)
foreach (var x in xs) ...
xs.Select(...)
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.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION