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);
};
1_jqrKgVeO-raAUJjuN-adug

Chamamos pipe com o nosso exemplo e veremos as maravilhas que aparecem.

1_rqi22p06rTtc2m0k1yHrRw

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.

1_UjM5plW8s--8chfQQg3cMg

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.

1_sEcE5tBFSpCzJZrKz8mmEQ

Agora, o currentValue é 'Buckethead', pois foi isso que retornou da última vez. currentFunction é uppercase, portanto 'BUCKETHEAD' será o próximo currentValue.

1_Va6taGFU8dSyhz1wLVMWMQ

É a mesma ideia, obter um certo número de caracteres de 'BUCKETHEAD', e entregá-los para a próxima função.

1_YaI1oxsZW5qZZUC146DYoQ

reverse('.etnemavon aiedi amsem A')

1*moIMQxr82r2Z8IuXwuZfKQ

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!