CodeGym /Cursos /C# SELF /Funções puras e imutabilidade

Funções puras e imutabilidade

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

1. Introdução

Função pura — é uma função que com os mesmos dados de entrada sempre retorna o mesmo resultado e não causa nenhum efeito colateral. Simplificando: uma função pura não "estraga" nada ao redor e não é afetada por fatores externos.

Uma função é considerada pura se:

  • Ela apenas calcula um valor com base nos argumentos de entrada.
  • Não modifica variáveis externas ou o estado do programa.
  • Não depende de variáveis externas ou estados que possam mudar.

As duas regras de ouro da pureza

  1. Determinismo: a mesma entrada — a mesma saída.
  2. Sem efeitos colaterais: a função não muda nada fora de si: nem arquivos, nem variáveis globais, nem a interface do usuário (oi, Console.WriteLine).

Não é religião, é bom senso

Pode parecer uma mania acadêmica, mas a prática mostra o contrário:

  • Funções puras são previsíveis. É fácil testá-las — você chama com argumentos específicos e obtém um resultado conhecido.
  • Uma função pura pode ser "movida" livremente no código sem medo de quebrar alguma coisa em outros lugares.
  • No mundo multicore, função pura é garantia de segurança: dá pra rodar em paralelo sem medo de race conditions.

2. Exemplos de funções puras e impuras

Função pura: tudo previsível

// Função pura: não toca nada fora dela
int Add(int a, int b)
{
    return a + b;
}

int Square(int x)
{
    return x * x;
}

Chame Add(2, 3) cem vezes — sempre vai retornar 5. Meio chato, mas confiável.

Função impura: quebra as regras

// Quebra a pureza: depende de estado externo (variável estática)
int counter = 0;

int Increase()
{
    counter++;
    return counter;
}

Aqui chamar Increase() vai devolver um valor diferente cada vez — ou seja, sem determinismo.

// Quebra a pureza: causa efeito externo (imprime na tela)
int AddAndPrint(int a, int b)
{
    int sum = a + b;
    Console.WriteLine(sum); // Efeito colateral!
    return sum;
}

E quanto a aleatoriedade e tempo?

Qualquer função que use DateTime.Now ou Random já não é pura:

// Não é pura!
int GetRandomNumber()
{
    return new Random().Next();
}

Tabela de diferenças

Característica Função pura Função impura
Sempre mesmo resultado para mesmos argumentos Sim Não
Efeitos colaterais Não Sim
Dependência de estado externo Não Sim

3. Imutabilidade de dados: teoria e prática

Imutabilidade (immutability) — é a abordagem em que um objeto não pode ser alterado após a criação. Se você precisa de um novo valor — cria um novo objeto.

Por que isso importa?

  • O app fica mais resistente a bugs acidentais por alteração de dados.
  • Não existem "vazamentos" secretos de mudanças: se você tem um objeto, ninguém vai mudar seus campos às escondidas.
  • Imutabilidade é base para muitas otimizações automáticas e para cálculos paralelos.

Exemplos simples em C#

Tipos imutáveis no .NET

Strings (string) em C# são imutáveis! Sempre que você faz string.Concat(s, "world"), uma nova string é criada.

string s = "Hello";
string t = s;
s = s + " World";
Console.WriteLine(t); // t == "Hello"

Arrays e coleções: mutáveis por padrão

int[] numbers = { 1, 2, 3 };
numbers[0] = 42; // O array mudou!

Imutabilidade na prática: compilando código

Ao invés de modificar um objeto/valor existente, retornamos um novo:

// Ao invés disso:
void AddToList(List<int> list, int value)
{
    list.Add(value); // Muta!
}

// Melhor assim:
List<int> AddToList(List<int> list, int value)
{
    var newList = new List<int>(list) { value }; // Nova lista
    return newList;
}

Ilustração: mudar vs não mudar

flowchart LR
    A[Objeto original] --"mutação"--> B[Mesmo objeto, mas diferente por dentro]
    A --"imutabilidade"--> C[Novo objeto]

4. Por que isso é útil em código C# real?

  • No C# moderno, bibliotecas como LINQ, Entity Framework e ASP.NET Core favorecem funções puras e imutabilidade.
  • Imutabilidade reduz a quantidade de bugs "mágicos", quando alguém em algum lugar sobrescreve um valor importante.
  • Funções puras permitem usar testes automáticos (unit tests) facilmente, porque o teste só precisa checar entrada e saída, sem se preocupar com o mundo externo.

Exemplo: trabalhando com strings

string s = "Hello";
string newS = s.Replace("H", "J"); // s continua "Hello"; newS é "Jello"

Exemplo: LINQ e coleções

Where, Select e outros métodos retornam novas coleções, sem modificar as antigas.

var numbers = new List<int> { 1, 2, 3, 4 };
var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();

// numbers permaneceu igual!

Exemplo do mundo real: configuração via objetos imutáveis

Muitos APIs modernos do .NET usam objetos imutáveis para configuração, por exemplo JsonSerializerOptions:

var options = new JsonSerializerOptions
{
    WriteIndented = true
};
// Esse objeto não é alterado "no meio do caminho", o que aumenta a confiabilidade.

5. Erros e armadilhas típicas

O caminho escorregadio começa quando você "acidentalmente" muta dados em código que deveria ser "puro".

Acontece muito com coleções: você quer filtrar uma lista, mas enquanto faz isso acaba mudando a original.

Ou esquece que um método como List<T>.Add modifica o objeto in-place.

Exemplo traiçoeiro:

List<int> DoubleTheNumbers(List<int> xs)
{
    // Erro! Mutamos a lista original, retornamos o mesmo objeto.
    foreach (var i in xs)
        xs.Add(i * 2);
    return xs;
}

Esse código vai até lançar uma exceção em tempo de execução (InvalidOperationException), porque estamos modificando a coleção enquanto a iteramos — problema clássico da mutação.

Correto:

List<int> DoubleTheNumbers(List<int> xs)
{
    var newList = new List<int>(xs.Select(x => x * 2));
    return newList;
}
2
Tarefa
C# SELF, nível 51, lição 1
Bloqueado
Lista imutável de números
Lista imutável de números
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION