Programação Funcional: Nem todas as funções são puras

Photo by Jimmy Chan from Pexels

No post anterior falei um pouco sobre um importante conceito da programação funcional: as funções puras. Esse tipo de função visa fornecer resultados consistentes e interferir o mínimo possível no funcionamento das demais funções de um programa. isso nos ajuda a criar código mais confiável e simples de ler, entender e alterar quando necessário (o que quase sempre acontece em qualquer software que esteja em constante melhoria e evolução). Mas, a pergunta que fica é: será que podemos construir um software somente utilizando funções puras? Para responder essa pergunta, vamos começar com um exemplo extremamente simples:

console.log("Olá mundo")

Talvez essa tenha sido a primeira linha de JavaScript escrita por muita gente. Mas, olhando para essa chamada de função, você diria que ela é pura ou não? Lembra que toda função pura precisa seguir 2 condições? Vamos ver se a função acima se encaixa:

  • Para as mesmas entradas, as mesmas saídas: SIM. Sempre que eu passar a string “Olá mundo”, a mesma string será printada no console.
  • É independente de dados utilizados por outras funções: NÃO. Quando a função console.log é chamada, ela dará o comando para que o navegador “desenhe’ o texto na tela. A tela é um recurso global, acessível por todos, ou seja, não é isolado. Portanto, essa função não é pura.

O mesmo vale para qualquer função que utilize outros recursos computacionais secundários (com exceção da memória e do processador). Gravou informação no disco? Não é pura. Enviou uma requisição a um serviço na rede ou um banco de dados? Não é pura. Contudo, a dependência referencial não é o único problema nesse caso. Vejamos o seguinte exemplo, escrito em Node.js, onde é criada uma conexão com um banco de dados MySQL:

var mysql = require('mysql');

var con = mysql.createConnection({
  host: "localhost",
user: "silva",
  password: "senha123"
});

con.connect(function(err) {
  if (err) throw err;
console.log("Conectado!");
});

Fica bem claro aqui que, nem sempre para as mesmas entradas a função de conexão retornará a mesma saída, e isso ocorre por que o banco de dados é um recurso externo e, pelo menos do ponto de vista do programa em execução, algo completamente imprevisível. Em uma situação normal, o banco estará funcionando corretamente e a conexão será efetuada. Mas, e se o banco estiver ocupado com outras requisições e não conseguir responder? E se estiver inacessível por conta de um problema na rede? E se o usuário e a senha forem inválidos? Em todos estes casos, em vez de retornar uma conexão, a função retornará um erro.

Observando estes exemplos, é possível perceber que é impossível viver sem as funções impuras. Ao desenvolver um software, quase sempre dependemos de dados ou recursos que estão fora do alcance das nossas funções, e que em muitos casos podem se comportar de maneiras que não esperamos. Não há nada que possamos fazer quanto a isso. Mas então, onde devemos utilizar as funções puras? Em toda função que possa ter um resultado previsível e independente de outros dados e recursos do sistema.

Esse conceito é especialmente útil em funções que envolvem cálculos matemáticos, dos mais cotidianos aos mais complexos. Quer calcular o valor total de uma venda, ou a porcentagem de imposto sobre uma transação? Crie uma função que recebe só os dados necessários (ex: produtos vendidos, alíquota de imposto) e obtenha o resultado sem deixar pontas soltas nem interferir em outros cálculos. Se você já desenvolve aplicações, vale a pena ter situações como essa em mente ao escrever novas funções ou refatorar código antigo. Cada caso é um caso, mas as funções puras podem te ajudar a melhorar muito a qualidade do seu código, além de evitar dores de cabeça futuras.

Em resumo: nem sempre podemos criar funções puras, mas quando existe a possibilidade, essa é geralmente a opção mais organizada e confiável

Fontes: