Vamos experimentar geradores funcionais e o operador de pipeline em JavaScript

Cristi Salcescu Blocked Unblock Seguir Seguindo 10 de janeiro Foto por Patrick Hendry em Unsplash

Um gerador é uma função que retorna o próximo valor da sequência toda vez que é chamado.

Vamos começar com um gerador funcional simples que dá o próximo inteiro cada vez que é chamado. Começa de 0.

 seqüência de funções () { 
deixe contar = 0;
função de retorno () {
const result = count;
contar + = 1;
resultado de retorno;
}
}
 const nextNumber = sequence (); 
nextNumber (); // 0
nextNumber (); // 1
nextNumber (); // 2

nextNumber() é um gerador infinito. nextNumber() também é uma função de fechamento.

Gerador finito

Geradores podem ser finitos. Verifique o próximo exemplo em que sequence() cria um gerador que retorna números consecutivos de um intervalo específico. No final da sequência, retorna undefined :

 sequência de funções (de, para) { 
deixe contar = de;
função de retorno () {
if (contar <para) {
const result = count;
contar + = 1;
resultado de retorno;
}
}
}
 const nextNumber = sequência (10, 15); 
nextNumber (); // 10
nextNumber (); // 12
nextNumber (); // 13
nextNumber (); // 14
nextNumber (); //Indefinido

listar()

Ao trabalhar com geradores, podemos criar uma lista com todos os valores da sequência. Para esta situação, precisamos de uma nova função toList() que toList() um gerador e retorne todos os valores da sequência como um array. A sequência deve ser finita.

 function toList (sequence) { 
const arr = [];
deixe o valor = sequence ();
while (valor! == indefinido) {
arr.push (valor);
valor = sequence ();
}
return arr;
}

Vamos usá-lo com o gerador anterior.

 números const = toList (sequência (10, 15)); 
// [10,11,12,13,14]

O operador de oleoduto

O operador de pipeline |> permite gravar transformações de dados de maneira mais expressiva. O operador de pipeline fornece açúcar sintático sobre as chamadas de função com um único argumento. Considere o próximo código:

 const shortenText = shortenText (capitalize ("Este é um texto longo")); 
 função capitalizar (texto) { 
return text.charAt (0) .toUpperCase () + text.slice (1);
}
 função shortenText (text) { 
return text.substring (0, 8) .trim ();
}

Com o operador de pipeline, a transformação pode ser escrita assim:

 const shortenText = "Este é um texto longo" 
|> capitalizar
|> shortenText;
//Isto é

Neste momento, o operador do pipeline é experimental. Você pode tentar usando o Babel:

  • no arquivo package.json adicione o plug-in do pipeline do babel:
 { 
"dependências": {
" @ babel / plugin-syntax-pipeline-op erator": "7.2.0"
 } 
}
  • no arquivo de configuração .babelrc adicione:
 { 
"plugins": [[" @ babel / plugin-proposal-pipeline- operator", {
"proposta": "mínimo"}]]
}

Geradores sobre coleções

Em Torne seu código mais fácil de ler com Programação Funcional Eu tive um exemplo de processamento de uma lista de todos . Aqui está o código:

 function isPriorityTodo (task) { 
return task.type === "RE" &&! task.completed;
}
 function toTodoView (task) { 
return Object.freeze ({id: task.id, desc: task.desc});
}
 const filtradoTodos = todos.filter (isPriorityTodo) .map (toTodoView); 

Neste exemplo, a lista de todos passa por duas transformações. Primeiro, uma lista filtrada é criada e, em seguida, é criada uma segunda lista com os valores mapeados.

Com geradores, podemos fazer as duas transformações e criar apenas uma lista. Para isso, precisamos de uma sequence() gerador sequence() que forneça o próximo valor de uma coleção.

 seqüência de funções (lista) { 
let index = 0;
função de retorno () {
if (index <list.length) {
const resultado = lista [índice];
índice + = 1;
resultado de retorno;
}
};
}

filter () e map ()

Em seguida, precisamos de dois decoradores filter() e map() , que funcionam com geradores funcionais.

filter() pega um gerador e cria um novo gerador que só retorna os valores da sequência que satisfaz a função predicado.

map() pega um gerador e cria um novo gerador que retorna o valor mapeado.

Aqui estão as implementações:

 filtro de função (predicado) { 
função de retorno (sequência) {
função de retorno filterSequence () {
valor constante = sequence ();
if (valor! == indefinido) {
if (predicado (valor)) {
valor de retorno;
} outro {
return filteredSequence ();
}
}
};
};
}
 mapa de funções (mapeamento) { 
função de retorno (sequência) {
função de retorno () {
valor constante = sequence ();
if (valor! == indefinido) {
mapeamento de retorno (valor);
}
};
};
}

Eu gostaria de usar esses decoradores com o operador de oleoduto. Então, ao invés de criar filter(sequence, predicate){ } com dois parâmetros, criei uma versão curry dela, que será usada assim: filter(predicate)(sequence) . Desta forma, funciona muito bem com o operador de pipeline.

Agora que temos a caixa de ferramentas, feita de funções sequence , filter , map e toList , para trabalhar com geradores sobre coleções, podemos colocar todas elas em um módulo ( "./sequence" ). Veja abaixo como reescrever o código anterior usando esta caixa de ferramentas e o operador de pipeline:

 import {sequence, filter, map, take, toList} de "./sequence"; 
 const filtradoTodos = 
seqüência (todos)
|> filtro (isPriorityTodo)
|> map (toTodoView)
|> toList;

reduzir()

Vamos dar outro exemplo que calcula o preço das frutas de uma lista de compras.

 function addPrice (totalPrice, line) { 
return totalPrice + (line.units * line.price);
}
 function areFruits (linha) { 
return line.type === "FRT";
}
 deixe fruitsPrice = shoppingList.filter (areFruits) .reduce (addPrice, 0); 

Como você pode ver, é necessário criar uma lista filtrada primeiro e depois calcular o total nessa lista. Vamos reescrever o cálculo com geradores funcionais e evitar a criação da lista filtrada.

Precisamos de uma nova função na caixa de ferramentas: reduce() . Leva um gerador e reduz a seqüência a um único valor.

 função reduce (accumulator, startValue) { 
função de retorno (sequência) {
let result = startValue;
deixe o valor = sequence ();
while (valor! == indefinido) {
resultado = acumulador (resultado, valor);
valor = sequence ();
}
resultado de retorno;
};
}

reduce() tem execução imediata.

Aqui está o código reescrito com geradores:

 import {sequence, filter, reduce} de "./sequence"; 
 const fruitsPrice = sequência (lista de compras) 
|> filtro (areFruits)
|> reduce (addPrice, 0);

leva()

Outro cenário comum é pegar apenas os primeiros n elementos de uma sequência. Para este caso, precisamos de um novo decorador take() , que receba um gerador e crie um novo gerador que retorne apenas os primeiros n elementos da sequência.

 função take (n) { 
função de retorno (sequência) {
deixe contar = 0;
função de retorno () {
if (contar <n) {
contar + = 1;
sequência de retorno ();
}
};
};
}

Novamente, esta é a versão curry de take() que deve ser chamada assim: take(n)(sequence) .

Aqui está como você pode usar take() em uma seqüência infinita de números:

 import {sequence, toList, filter, take} de "./sequence"; 
 function isEven (n) { 
return n% 2 === 0;
}
 const first3EvenNumbers = sequence () 
|> filtro (isEven)
|> pegue (3)
|> toList;

// [0, 2, 4]

Geradores personalizados

Podemos criar qualquer gerador personalizado e usá-lo com a caixa de ferramentas e o operador de pipeline. Vamos criar o gerador personalizado Fibonacci:

 função fibonacciSequence () { 
seja a = 0;
seja b = 1;
função de retorno () {
const aResult = a;
a = b;
b = aResult + b;
return aResult;
};
}
 fibonacci const = fibonacciSequence (); 
fibonacci ();
fibonacci ();
fibonacci ();
fibonacci ();
fibonacci ();
 const firstNumbers = fibonacciSequence () 
|> pegue (10)
|> toList;

// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Conclusão

O operador de pipeline torna a transformação de dados mais expressiva.

Geradores funcionais podem ser criados sobre seqüências finitas ou infinitas de valores.

Com geradores, podemos fazer o processamento de listas sem criar listas intermediárias em cada etapa.

Você pode verificar todas as amostras em codesandbox . Eu gostaria de ouvir seus pensamentos sobre essa abordagem. Para mais informações sobre o lado funcional do JavaScript, dê uma olhada em:

Descubra a programação funcional em JavaScript com esta introdução completa

Descubra o poder dos closures em JavaScript

Como composição livre de pontos fará de você um melhor programador funcional

Como melhorar seu código com nomes de função que revelam a intenção

Aqui estão alguns decoradores de função que você pode escrever do zero

Torne seu código mais fácil de ler com a programação funcional