Programação Funcional: O que são funções puras?

Photo by Tyler Lastovich from Pexels

Depois de muito tempo resolvi escrever posts focados em programação aqui no Bluend, e este é o primeiro deles. A minha intenção é falar de forma simples sobre alguns assuntos espinhosos ou dos quais não é possível encontrar muito material em Português (o que, querendo ou não, ainda é um obstáculo para muitos devs BRs). De cara, já vou começar falando de um conceito que está em alta atualmente, mas que muita gente ainda não aplica ou sabe pouca a respeito: programação funcional. E só de olhar pro conceito, que contém a palavra “matemática”, acho que muita gente fica de cabelo em pé:

Na Ciência da Computação, programação funcional é um paradigma de programação — um estilo de construir a estrutura e elementos de um programa de computador — que trata a computação como a avaliação de funções matemáticas e evita mudanças de estados e dados mutáveis – Wikipedia (traduzido)

Bom, trocando em miúdos: a programação funcional é uma forma de programar baseada em funções. Essas funções são construídas seguindo alguns padrões, o que pode ajudar a deixar o código mais organizado e compreensível. Vamos falar nesse post de um desses padrões, o qual você provavelmente você já tenha visto na escola da seguinte forma:

f(x) = x + 1

Lembra disso? Essa é uma função matemática. Lembra da parte do conceito que fala sobre “avaliação de funções matemáticas”? Pois é, avaliar uma função é “resolver” ela, ou seja, testar um valor (entrada) e ver qual resultado a função irá retornar (saída). Sem perceber, a gente faz isso na escola quando, por exemplo, o professor nos pede que, para a função acima, consideremos x = 2.

f(2) = 2 + 1
f(2) = 3

Nesse caso, informamos o valor 2 e a função nos retornou o valor 3. Para esta função, isso SEMPRE irá acontecer. f(2) é sempre igual a 3, faça chuva ou faça sol. Para cada valor de entrada, temos um valor de saída, e para as mesmas entradas sempre obteremos as mesmas saídas. Funções que têm essas características podem ser chamadas de funções puras. Mas afinal, como relacionar isso com o mundo da programação? Bom, vamos para um exemplo simples em JavaScript:

function geraAleatorio(numero) {
return Math.random() + numero
}
geraAleatorio(2) //2.8664869963274864
geraAleatorio(2) //2.871446099392515
geraAleatorio(2) //2.020983199545031

Essa função funciona (com o perdão do trocadilho) de forma diferente da que vimos anteriormente. Para a mesma entrada (o número 2) obtemos diferentes saídas, afinal, estamos utilizando a função “random” que gera números aleatórios a cada execução. Ou seja, esta não é uma função pura. Mas não são apenas as entradas e saídas que definem a “pureza” de uma função. Uma função pura deve fazer suas operações de forma isolada, sem influenciar nos valores utilizados por outras funções. Consideremos o seguinte exemplo:

 var minhaSoma = 0
var minhaSub = 0
function fazerSoma(valor1, valor2) {
minhaSoma = valor1 + valor2
}

function fazerSub(valor1, valor2) {
minhaSub = valor1 - valor2
}

function mostraTela(){
console.log("Soma: " + minhaSoma)
console.log("Subtração: " + minhaSub)
}
fazerSoma(2,2)
fazerSub(2,2)
mostraTela()

/*
Resultado:
Soma: 4
Subtração: 0
*/

Nesse exemplo, temos 2 funções simples, uma que realiza somas e outra subtrações. Por fim, uma terceira função printa os resultados na tela. Só que essa estrutura tem um problema: dependemos das variáveis “minhaSoma” e “minhaSub” (que estão no topo do código) para armazenar os resultados. Nada no código impede que o valor dessas variáveis seja modificado, e isso pode causar inconsistências. Imagine que um programador descuidado (ou mal-intencionado) modifique manualmente um dos valores já calculados:

fazerSoma(2,2)
fazerSub(2,2)
minhaSub = 1
mostraTela()

/*
Resultado:
Soma: 4
Subtração: 1
*/

Bom, como você já percebeu, 2 – 2 não pode ser igual a 1 de forma alguma, apesar de o programa acima estar mostrando esse resultado. Com apenas 1 linha de código a mais, foi possível de inserir uma inconsistência em nossos cálculos. Mas isso só foi possível pois dependemos do valor da variável “minhaSub” para mostrar o resultado. Isso se chama dependência referencial, e é exatamente isso que precisamos evitar ao criar uma função pura. Sem referências globais/externas, podemos construir um código mais confiável e menos suscetível a erros. Veja esse mesmo programa reescrito de maneira a tornar puras as funções de soma e subtração. Além de mais confiável, o código fica menor também:

function fazerSoma(valor1, valor2) {
return valor1 + valor2
}
function fazerSub(valor1, valor2) {
return valor1 - valor2
}
function mostraTela(valor){
console.log(valor)
}
mostraTela(fazerSoma(2,2)); //valor sempre vai ser 4, impossível mudar fora da função
mostraTela(fazerSub(2,2)); //valor sempre vai ser 0, impossível mudar fora da função

Em resumo, uma função pura:

  • Para as mesmas entradas, retorna sempre as mesmas saídas
  • Não interfere nos valores utilizados por outras funções, e nem depende deles

Fontes: