Artigo original: A quick introduction to pipe() and compose() in JavaScript
A programação funcional tem sido reveladora para mim. Neste artigo e em artigos como ele, tentarei compartilhar minhas visões e perspectivas à medida que desbravo um pouco mais da programação funcional.
A Ramda tem sido minha biblioteca de Programação Funcional favorita, em função da facilidade com que torna a programação em JavaScript funcional. Eu a recomendo demais.
Pipe
O conceito de pipe
é simples - ele combina n
funções. É uma canal que flui, da esquerda para a direita, chamando cada função como a saída da anterior.
Vamos escrever uma função que retorne o nome de alguém.
getNome = (pessoa) => pessoa.nome;
getNome({ nome: 'Criatura' });
// 'Criatura'
Vamos escrever uma função que coloque as strings em maiúsculas.
maiusculas = (string) => string.toUpperCase();
maiusculas('Criatura');
// 'CRIATURA'
Portanto, se quiséssemos obter o nome da pessoa e colocá-lo em maiúsculas, poderíamos fazer assim:
nome = getNome({ nome: 'Criatura' });
maiusculas(nome);
// 'CRIATURA'
Tudo bem, mas vamos eliminar esse nome de variável intermediária.
maiusculas(getNome({ nome: 'Criatura' }));
Melhor, mas eu ainda não gosto muito desse aninhamento. Pode ficar muito lotado. Se quisermos acrescentar uma função que receba as primeiras 4 letras de uma string, como será?
get4Letras = (string) => string.substring(0, 4);
get4Letras('Criatura');
// 'Cria'
Resultando em:
get4Letras(maiusculas(getName({ nome: 'Criatura' })));
// 'CRIA';
Vamos dar uma enlouquecida e acrescentar uma função para inverter as strings.
inverter = (string) =>
string
.split('')
.reverse()
.join('');
inverter('Criatura');
// 'arutairC'
Agora, temos:
inverter(get4Letras(maiusculas(getNome({ nome: 'Criatura' }))));
// 'AIRC'
Pode ficar um pouco... demais.
Vamos usar o pipe para salvar o dia!
Em vez de encher de funções dentro de funções ou criar diversas variáveis intermediárias, vamos canalizar isso tudo!
pipe(
getNome,
maiusculas,
get4Letras,
inverter
)({ nome: 'Criatura' });
// 'AIRC'
Agora, sim. Ficou como se fosse uma lista de tarefas!
Vamos analisar. Para fins de demonstração, vou usar uma implementação de pipe
de um dos artigos de programação funcional do Eric Elliott.
pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x);
Adoro esse código de uma só linha.
Usando parâmetros rest (veja meu artigo sobre isso – texto em inglês) podemos canalizar n funções. Cada função toma a saída da anterior e é tudo reduzido a um único valor.
Você pode usar esse valor exatamente como fizemos acima.
Nota do tradutor: desta vez, deixaremos o exemplo (e as variáveis e nomes de função) em inglês, pois as imagens fazem referência a elas.
pipe(
getName,
uppercase,
get6Characters,
reverse
)({ name: 'Buckethead' });
// 'TEKCUB'
Vou expandir o pipe
, acrescentar algumas declarações de depuração, e vamos linha por linha.
pipe = (...functions) => (value) => {
debugger;
return functions.reduce((currentValue, currentFunction) => {
debugger;
return currentFunction(currentValue);
}, value);
};
Chamamos pipe
com o nosso exemplo e veremos as maravilhas que aparecem.
Confira as variáveis locais. functions
é um conjunto das 4 funções e value
é { name: 'Buckethead'
}.
Como usamos parâmetros rest, o pipe
permite que qualquer número de funções seja usado. Ele apenas percorre e chama cada uma delas.
No próximo depurador, estremos dentro do reduce
. É aqui que currentValue
é passado para currentFunction
e retornado.
Vemos que o resultado é 'Buckethead'
, pois currentFunction
retorna a propriedade .name
de qualquer objeto. Isso será retornado em reduce
, o que significa que se tornará o novo currentValue
da próxima vez. Vamos ao próximo depurador e ver.
Agora, o currentValue
é 'Buckethead'
, pois foi isso que retornou da última vez. currentFunction
é uppercase
, portanto 'BUCKETHEAD'
será o próximo currentValue
.
É a mesma ideia, obter um certo número de caracteres de 'BUCKETHEAD'
, e entregá-los para a próxima função.
reverse('.etnemavon aiedi amsem A')
Pronto!
E quanto ao compose()?
É a mesma coisa que pipe
, só que na direção inversa. Portanto, se você quisesse o mesmo resultado que nosso pipe
acima, você faria o contrário.
compose(
reverse,
get6Characters,
uppercase,
getName
)({ name: 'Buckethead' });
Observou como getName
é o último na cadeia e reverse
é o primeiro?
Aqui temos uma rápida implementação do compose
, novamente cortesia do mágico Eric Elliott, a partir do mesmo artigo (texto em inglês):
compose = (...fns) => (x) => fns.reduceRight((v, f) => f(v), x);
Vou deixar a expansão dessa função com as depurações como um exercício para você. Aprenda com o código, use-o, modifique-o. Mais importante ainda, divirta-se!