O que são funções referenciais na prática
Muita gente confunde função referencial com qualquer função que usa uma referência. Não é bem assim. Uma função referencial é aquela cujo resultado depende exclusivamente dos argumentos recebidos. O mesmo input sempre produz o mesmo output, sem importar o estado do sistema, variáveis globais, ou efeitos colaterais. Isso se chama transparência referencial e é um conceito da programação funcional, mas pode ser aplicado em qualquer linguagem. A função Math.sqrt(4) em JavaScript é referencial. A função console.log() não é, porque altera o estado externo e não retorna um valor determinístico no sentido estrito.
O que é função referencial e por que eu me importaria
Na minha experiência, o maior impacto prático não é estética de código. É testabilidade e raciocínio sobre o que o programa faz. Se sua função é referencial, você pode substituir a chamada dela pelo valor que ela retorna sem alterar o comportamento do programa. Isso parece útil até você encontrar um projeto onde 80% das funções acessam estado global de alguma forma. Aqui vai algo que ninguém avisa: funções referenciais exigem que os dados sejam imutáveis ou tratados como tal. Se você passa um array e a função modifica ele in-place, já quebrou a regra, mesmo que o retorno pareça correto. Eu perdi duas horas debugando um código em Python que parecia referencial até perceber que um list.sort() estava sendo chamado dentro de uma delas. Funções puras precisam receber cópias dos dados quando precisam ordenar ou transformar listas.
Como construir funções referenciais
A receita básica é simples, mas a execução costuma ser mais complicada. Você precisa garantir três coisas: sem dependência de estado externo mutável, sem efeitos colaterais observáveis, e determinismo absoluto no resultado. Regra número um: nenhuma leitura de variáveis globais, contexto, hora do sistema, ID gerado automaticamente, ou acesso a rede. Tudo que a função precisa deve estar nos parâmetros ou ser constante imutável definida no momento da declaração.
Regra número dois: se você precisa de dados mutáveis, copie antes de manipular. Em JavaScript, usar {...obj} ou structuredClone() resolve na maioria dos casos. Em Rust, isso já é parte da linguagem por padrão porque os ownership rules forçam esse comportamento. Regra número três: se a função invoca outra função, verifique se essa função também é referencial. Você herda todos os efeitos colaterais das dependências. Eu tive um caso em que uma função de cálculo financeiro parecia pura, mas chamava internamente um método de logging que escrevia em um arquivo com timestamp. O resultado do cálculo era correto, mas a função como um todo não era referencial porque o acto de chamá-la alterava o sistema de arquivos.
Pegadinhas avançadas que você provavelmente não conhece
Existe uma nuance importante que a maioria dos tutoriais ignora. Funções que usam objetos como chave de mapa em linguagens dinâmicas podem escapar da referencialeidade se esses objetos sofrerem mutações entre chamadas. Em JavaScript, por exemplo, se você passa um objeto como parâmetro e usa ele como chave de um WeakMap dentro da função, e esse objeto é mutado externamente entre duas chamadas, o comportamento muda sem que o código aparente da função mude. Outro problema real é a precisão de ponto flutuante. Duas funções matematicamente idênticas podem retornar valores ligeiramente diferentes em implementações diferentes de bibliotecas numéricas. Isso quebra a garantia de resultado idêntico para mesmo input em cenários de paralelismo ou quando diferentes compiladores otimizam o código de formas distintas. Em projetos científicos, eu resolvi isso padronizando a biblioteca de matemática e usando tolerância de comparação em vez de igualdade estrita nos testes de unidade.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Também vale mencionar que referencialidade total é difícil em sistemas que precisam interagir com o mundo real. APIs HTTP, bancos de dados, arquivos, sockets — tudo isso introduz efeito colateral. A solução habitual não é tornar tudo referencial, mas isolar as fronteiras. Separe a lógica de cálculo da lógica de E/S. A primeira é referencial. A segunda lida com efeitos e fica no perímetro do programa.
Quando funções referenciais falham
Funções referenciais não são bala de prata. Elas não resolvem problemas de concorrência sozinhas — você ainda precisa de imutabilidade de dados e sincronização adequada. Elas podem gerar overhead de memória significativo quando copies profundas são necessárias a cada chamada, especialmente em linguagens sem garbage collection eficiente ou em sistemas embarcados. Em projetos existentes com muito código imperativo, transformar tudo em funções referenciais pode exigir refatoração massiva que não compensa o ganho. Nestes casos, a estratégia mais pragmática é identificar os pontos críticos de lógica de negócio e torná-los referenciais, mantendo o resto do código como está. Eu já vi times tentarem migrar 100% para pure function e desistir depois de três meses porque o custo era absurdamente alto comparado ao benefício real.
Para código que realmente precisa de efeitos — e a maioria precisa — considere usar monads de efeito, como Result ou Either, ou abstrações do tipo IO em linguagens funcionais, ou simplesmente funções que retornam structs de resultado com status explícito em linguagens imperativas. Isso mantém a lógica principal referencial enquanto trata efeitos de forma visível e gerenciável.
Exemplo prático rápido
Função não-referencial: let contador = 0; function aumentar() { contador++; return contador; }
Depende de estado externo. Chamadas repetidas produzem resultados diferentes. Versão referencial equivalente:
function aumentar(n) { return n + 1; } O estado precisa ser passado como argumento e retornado como parte do resultado. Quem chama é responsável por acumular o estado. Parece mais verboso. É mais previsível.