Entendendo o básico de programação funcional com Javascript - Parte 1
26/03/2019 • Raphael Kieling Tondin
Sou apaixonado por programação funcional, é um sintaxe linda e organizada que deixa qualquer programador de outro paradigma curioso. Hoje vamos ver alguns conceitos sobre FP (para os intimos), está é a parte 1/2 sobre programação funcional.
Sumário
- Porque eu estudaria isto?
- Quem usa?
- O que é Programação Funcional?
- Conceitos chaves
- Exemplo Final
- Finalizando
- Referências
Porque eu estudaria isto?
Alguns pontos fazem dessa abordagem uma ótima escolha:
- Funções puras evitando assim efeito colaterais
- Reduz a complexidade, se
A + B
então é pra somar, não para somar, ir no banco e dar log no console. - Facilmente testável.
(a , b) => a + b;
Da pra testar isso só de olhar hehe. - Trabalha facilmente com concorrência
Quem usa?
Spark, Netflix, Google, Facebook, Amazon (Amazon Lambda), sistemas de avião como da família Airbus A340, entre muitos outros.
O que é Programação Funcional?
Programação funcional e para os mais íntimos FP, é um paradígma de programação como Orientação a Objetos (OOP). Entre elas ainda temos:
- Procedural
- Meta Programação
- Imperativa
- Declarativa
- ...
Ok, acho que tu já entendeu. Mas entenda que um paradígma dita a filosofia de uma linguagem em certo ponto, na orientação a objetos
nós temos entidades que servem de base para nós trabalharmos pensando no sistema como objetos em si, na procedural
definimos linha a linha o que vai ser feito como uma receita de bolo. O FP modifica a linha de raciocínio para algo mais volta a obviamente funções
de forma mais declarativa. Que tal um exemplo?
let pessoas = [
{ nome: 'Raphael', idade: 21 },
{ nome: 'Emilio', idade: 30 },
{ nome: 'Jeromel', idade: 13 }
];
// Imperativo é focado em COMO ele opera
let filtradosMenorIdade = [];
for(let i=0; i < pessoas.length; i++){
if(pessoa.idade < 18){
let pessoa = pessoas[i];
pessoa.nome = pessoa.nome.toUpperCase();
filtradosMenorIdade.push(pessoa);
}
}
// Declarativo
function colocaNomeParaMaiusculo(person){
person.nome = person.nome.toUpperCase();
return person;
}
function isMenorDeIdade(pessoa){
return pessoa.idade < 18
}
let novaListagemMenoresDeIdade = pessoas
.filter(isMenorDeIdade)
.map(colocaNomeParaMaiusculo)
Estranho? Não te preocupa, isto faz sentido. Olhando com calma nós vemos uma diferença muito simples que é facil de pegar.
- Primeiro abordagem (Imperativa)
- De forma imperativa criamos um
for
baseado no tamanho do array, passamos pela listagem verificando quem é menor de idade. - Sendo menor de idade colocamos o nome para maisculo e adicionamos na lista chamada
filtradosMenorIdade
- De forma imperativa criamos um
- Segunda abordagem (Declarativa)
- Filtramos as pessoas verificando quem é menor de idade
- Mapeamos a nova listagem vinda do
filter
de forma recursiva colocando tudo para maiúsculo e retorna paranovaListagemMenoresDeIdade
.
Vai dizer. Fácil né.
Conceitos chaves
Funções de primeira classe
O objetivo é evitar o state
e abstrair para ser o mais reutilizavel possível, aceitando até mesmo outras funções como argumento. De modo geral as funções .map()
e .filter()
fazem exatamente isto, aqui vai um exemplo do livro Eloquent Javascript:
function greaterThan(n) {
return function(m) {
return m > n;
};
}
var greaterThan10 = greaterThan(10);
A função greaterThan
devolve uma função, então quer dizer que ela pode ser reutilizada, utilizando clojure que é quando utilizamos aquela variável n
dentro da segunda função function(m){...}
Função Matemática ou Função Pura
Na matemática a função é relação entre as entradas e as saídas em que cada combinação de entrada seja exatamente a mesma saída. Na programação é a mesma coisa mas além disso NÃO alterar nada que esteja fora da função evitando assim efeitos colaterais.
function pegaMaiorNumero(a, b){
return a > b ? a : b
}
pegaMaiorNumero(5, 30); // Output: 30
pegaMaiorNumero(5, 30); // Output: 30
Recursividade
FP não é FP sem a famosa recursividade. Recursividade nada mais é que um função que chama ela mesmo, olha só:
let lista = [1,2,3];
// Sem recursividade
for(let i=0; i < lista.length; i++){
console.log(lista[i]); // Output: 1,2,3
}
// Com recursividade
lista
.forEach(numero => console.log(numero)); // Output: 1,2,3
Ela chama ela mesma até finalizar o array.
Ditar O QUE ser feito e não COMO
No primeiro exemplo utilizamos o for
"normal" e na segunda abordagem utilizam uma maneira de ditar o que devia ser feito sem ter que explicitar. Temos algumas funções como:
- Filter
- Reduce
- Map
- Some
- Find
- Every
Quer filtrar quem tem poder mais de 8.000 e logo depois pegar o que tem o maior poder dentre os filtrados? Sem problemas.
let listaParaFiltrar = [100, 6000,30000,2000, 1000, 6050];
function temMaisQuePoderMinimo(poderMinimo){
return poder => poder > poderMinimo;
}
function pegaMaisPoderoso(prev, now){
return prev > now ? prev : now;
}
let osMelhores = listaParaFiltrar
.filter(temMaisQuePoderMinimo(8000))
.reduce(pegaMaisPoderoso)
console.log(osMelhores); // Output: 30000
Efeitos Colaterais
Talvez o mais importante conceito da programação funcional, evitar fazer o que não deveria estar fazendo!
function adicionarPessoaNoBanco(pessoa){
pessoa.ativa = true;
// Lógica de adicionar no banco ...
// bla bla bla
}
let pessoa = { nome: 'Damião' };
adicionarPessoaNoBanco(pessoa);
Olha bem pra isto. Tá vendo algo errado? pessoa.ativa = true
. Isto não deveria estar aqui, deveria? Como é que alguem vai saber que na hora que tu adicionar ele já vai colocar como ativa? Sou vidente? Vamos fazer algumas mudanças:
function adicionarPessoaNoBanco(pessoa){
// Lógica de adicionar no banco ...
}
function colocaPessoaParaAtiva(pessoa){
pessoa = Object.assign({}, pessoa);
pessoa.ativa = true;
return pessoa;
}
let pessoa = { nome: 'Damião', ativa: false };
pessoa = colocaPessoaParaAtiva(pessoa);
adicionarPessoaNoBanco(pessoa);
Imutabilidade
O conceito supremo. A função não deve modificar o dado original! Sempre retorne o novo estado assim evita-se de trabalhar em um objeto onde algo já o modificou.
let pessoa = {
nome: 'Duck'
};
// Errado!
function alteraNomePessoaErrado(pessoa){
pessoa.nome = 'Damião';
return pessoa;
}
// Corretão
function alteraNomePessoaCerto(pessoa){
pessoa = Object.assign({}, pessoa);
pessoa.nome = 'DJ Rogerinho'
return pessoa;
}
alteraNomePessoaErrado(pessoa);
console.log(pessoa.nome); // Output: Damião
alteraNomePessoaCerto(pessoa);
console.log(pessoa.nome); // Output: Damião
Percebeu que o alteraNomePessoaCerto
não alterou o nome? Exatamente. Utilizamos um cara chamado Object.assing
onde criamos uma nova referência em memória evitando assim trabalhar no objeto original.
Exemplo Final
O objetivo é ir na API de cep, perguntar por um cep válido, tratar o retorno e dar um console.log
do resultado.
/**
* Esta função cria um sistema de composição de função onde ao passar várias funções
* ela executa uma em sequência da outra passando o resultado da função anterior
* como parâmetro para próxima.
* */
function compose(...fn){
return (objectToJob) => fn.reduce((ant, now)=> now(ant), objectToJob)
}
/**
* Retorna uma Promise com o resultado da requisição em json
* */
function getCepResponseDataInJSON(cepNumber){
return fetch(`https://viacep.com.br/ws/${cepNumber}/json/`)
.then(response => response.json());
}
/**
* Retorna [uf] localidade - cep
* ao passar um cep válido
* */
function getCepInformationString(cepNumber){
let getLocateAndUf = ({ localidade, uf, cep }) => ({ localidade, uf, cep }) ;
let concatNameWithCep = ({ localidade, uf, cep }) => `[${uf}] ${localidade} - ${cep}`;
return getCepResponseDataInJSON(cepNumber).then(response => {
/**
* Executa getLocateAndUf pega o resultado e passa para concatNameWithCep
* getLocateAndUf -> concatNameWithCep
*
* O response ali no final serve para alimentar a primeira função no caso
* a getLocateAndUf
*
* Olhe como fica mais organizado perto de outras abordagens como
* ----
* let locateWithUf = getLocateAndUf(response);
* let concated = concatNameWithCep(locateWithUf);
* return concated;
* --- ou
* return concatNameWithCep(locateWithUf(response));
* */
return compose(getLocateAndUf, concatNameWithCep)(response);
});
}
getCepInformationString('95555000')
.then(console.log);
// Output: [RS] Capão da Canoa - 95555-000
Finalizando
Deixamos de passar em alguns conceitos bem legais, mas que considero mais avançados comos:
- Memoize
- Curry
- Compose
- Pipe
- Mônadas
Mas acho que por enquanto já foi mais do que necessário pra entender os conceito básicos. Caso tu queira algumas bibliotecas para deixar o Javascript mais legal ainda de trabalhar com programação funcional de uma olhada em: