Desenvolvimento

31 jan, 2019

Programação funcional com JavaScript –  Composição

Publicidade

Neste artigo vamos falar sobre Composição, o que é, como resolver um determinado problema com isso e qual problema ela pode vir a resolver.

Composição

É quando podemos reunir várias funções em uma – onde a mesma recebe um número variado de funções por parâmetro, e dentro dela é usado o resultado de uma como entrada da outra. Vamos exemplificar com código, que talvez fique mais claro o entendimento.

O problema

Vamos imaginar que estamos sentados em nossa mesinha, ai o gerente chega com a gente e fala que precisa que façamos um novo módulo front-end para o sistema MarombaWebApp, e que nesse módulo, nosso amiguinho do back-end vai devolver uma lista de usuários onde nela possui alguns dados.

Imagine que sendo um deles a quantidade de parcelas faltantes e outro o valor de cada parcela, e que na nossa tela precisamos mostrar a lista de todos os usuários e o total que falta para eles pagarem. Precisamos desse controle e o prazo é pra daqui a uma hora.

Dado esse problema e o prazo apertado, precisamos entregar aquilo funcionando logo. Nós nem pensamos na qualidade, só pensamos que estamos já atrasados para entregar. Com a descrição do problema, a solução já veio na mente, uma lista é comum de receber e pensamos logo em iterar essa lista e somar o montante. Logo nosso problema estaria resolvido e ficaria dessa forma:

const usuarios = [
  { nome: 'Aluno 1', parcelasFaltantes: 3, valor: 89.9 },
  { nome: 'Aluno 2', parcelasFaltantes: 5, valor: 19.9 },
  { nome: 'Aluno 1', parcelasFaltantes: 7, valor: 49.9 },
  { nome: 'Aluno 1', parcelasFaltantes: 6, valor: 69.9 },
  { nome: 'Aluno 1', parcelasFaltantes: 2, valor: 99.9 }
]

let total = 0

usuarios.forEach(usuario => {
  total += usuario.parcelasFaltantes * usuario.valor
})

console.log('valor total faltante =>', total)

Onde temos uma lista fictícia, uma variável global para receber o nosso total e um laço de repetição onde pegaremos o valor total de cada usuário.

Aparentemente não temos problema algum, não é mesmo? Mas vamos supor que precisamos dessa mesma lógica em outro módulo do sistema, dado uma lista, eu preciso da somatória do total – o que faremos? Isso mesmo que você pensou: Ctrl + C e Ctrl + V. Começando, assim, a repetição de código em todo os nosso sistema, olha que lindeza.

A solução

Como vocês já devem ter imaginado, para solucionar esse nosso pequeno problema, vamos utilizar a técnica de composição, e com isso aplicaremos algo conhecido pela maioria dos Devs – o princípio da responsabilidade única, o famoso S do SOLID.

Nosso primeiro passo vai ser separar as responsabilidades, onde teremos uma função para realizar o cálculo do valor total por usuário (linha 9) e outra função, onde será realizada a somatória total do valor faltante de todos os usuários (linha 12):

const usuarios = [
  { nome: 'Aluno 1', parcelasFaltantes: 3, valor: 89.9 },
  { nome: 'Aluno 2', parcelasFaltantes: 5, valor: 19.9 },
  { nome: 'Aluno 1', parcelasFaltantes: 7, valor: 49.9 },
  { nome: 'Aluno 1', parcelasFaltantes: 6, valor: 69.9 },
  { nome: 'Aluno 1', parcelasFaltantes: 2, valor: 99.9 }
]

const totalPorUsuario = usuario =>
  usuario.parcelasFaltantes * usuario.valor;

const somatorioTotal = usuarios =>
  usuarios.reduce((total, usuario) => total += totaPorUsuario(usuario), 0);

console.log('valor total faltante =>', somatorioTotal(usuarios))

Temos o mesmo resultado da nossa solução onde está tudo junto e misturado, só que dessa vez, cada coisa está no seu devido lugar, fazendo só o que precisa fazer, porém, podemos dar uma melhorada e separar a soma que é feita dentro da função somatorioTotal e criar uma outra função de soma de forma que ela se tornará genérica o suficiente para ser usada em qualquer outro lugar.

const usuarios = [
  { nome: 'Aluno 1', parcelasFaltantes: 3, valor: 89.9 },
  { nome: 'Aluno 2', parcelasFaltantes: 5, valor: 19.9 },
  { nome: 'Aluno 1', parcelasFaltantes: 7, valor: 49.9 },
  { nome: 'Aluno 1', parcelasFaltantes: 6, valor: 69.9 },
  { nome: 'Aluno 1', parcelasFaltantes: 2, valor: 99.9 }
]

const totalPorUsuario = usuario =>
  usuario.parcelasFaltantes * usuario.valor;

const soma = (a, b) => a + b;

const somatorioTotal = usuarios =>
  usuarios.map(totalPorUsuario).reduce(soma, 0);

console.log('valor total faltante =>', somatorioTotal(usuarios))

No JavaScript não existem funções que aceitem composição de forma nativa, por esse motivo implementaremos as funções de map e reduce onde a composição é aceita.

Elas terão a seguinte estrutura:

const map = fn => xs => xs.map(fn);

const reduce = (fn, ini) => xs => xs.reduce(fn, ini)

A função map recebe apenas uma função por parâmetro e retorna uma nova função que exige um parâmetro apenas e o parâmetro é o dado que será processado.

E temos na terceira linha a reatribuição da função reduce, onde nela temos dois parâmetros, sendo o primeiro a função que será usada para realizar o processamento dos dados e a segunda é o valor inicial. Com isso temos algo padronizado, onde a saída de uma função é a entrada da próxima.

Assim podemos organizar de forma mais eficiente o nosso código e torná-lo mais legível. As coisas vão se encaixando e fazendo sentido e fica claro o fluxo dos dados.

// Dados obtidos de algum lugar
const usuarios = [
  { nome: 'Aluno 1', parcelasFaltantes: 3, valor: 89.9 },
  { nome: 'Aluno 2', parcelasFaltantes: 5, valor: 19.9 },
  { nome: 'Aluno 1', parcelasFaltantes: 7, valor: 49.9 },
  { nome: 'Aluno 1', parcelasFaltantes: 6, valor: 69.9 },
  { nome: 'Aluno 1', parcelasFaltantes: 2, valor: 99.9 }
]

// Reatribuicao das funcoes map e reduce para suportar composicao 
const map = fn => xs => xs.map(fn);

const reduce = (fn, ini) => xs => xs.reduce(fn, ini)

//Regra de negocio
const totalPorUsuario = usuario =>
  usuario.parcelasFaltantes * usuario.valor;

//Separacao de responsabilidade, usando a composicao do map
const calculoTotalUsuarios = map(totalPorUsuario);

// Funcao generica
const soma = (a, b) => a + b;

// Reducao de todos os usuarios, somando todos e comecando a partir de 0
const somaTodosUsuarios = reduce(soma,0)

// Composicao das funcoes calculoTotalUsuarios e somaTodosUsuarios
const somatorioTotal = usuarios =>
  somaTodosUsuarios(calculoTotalUsuarios(usuarios))

console.log('valor total faltante =>', somatorioTotal(usuarios))

Essa série de programação funcional com JavaScript foi feita de forma rápida e direta. Esse artigo, exclusivamente, tive que estudar bastante e ler muitos outros sobre o assunto. Se você quer se aprofundar, recomendo ler os artigos e o vídeo de referência.

Principais referências: