Автор оригинала: Cristian Salcescu.
Откройте для себя функциональный JavaScript был назван одним из Лучшие новые функциональные программирования книги BookAuthority Действительно
Объединение функциональных генераторов с помощью оператора трубопровода и чистыми функциями с намерением выявления имен, позволяет более выразительно написать код, не создавая промежуточные списки:
import { sequence, filter, map, take, toList } from "./sequence";
const filteredTodos =
sequence(todos)
|> filter(isPriorityTodo)
|> map(toTodoView)
|> take(10)
|> toList;Давайте посмотрим, как.
Я начну с простого функционального генератора, который дает следующее целое число каждый раз, когда вызывается. Начинается от 0.
function sequence() {
let count = 0;
return function() {
const result = count;
count += 1;
return result;
}
}
const nextNumber = sequence();
nextNumber(); //0
nextNumber(); //1
nextNumber(); //2NextNumber () это бесконечный генератор. NextNumber () также является функцией закрытия.
Конечный генератор
Генераторы могут быть конечными. Проверьте следующий пример, где Последовательность () Создает генератор, который возвращает последовательные числа из определенного интервала. В конце последовательности она возвращается undefined :
function sequence(from, to){
let count = from;
return function(){
if(count< to){
const result = count;
count += 1;
return result;
}
}
}
const nextNumber = sequence(10, 15);
nextNumber(); //10
nextNumber(); //12
nextNumber(); //13
nextNumber(); //14
nextNumber(); //undefinedк списку()
При работе с генераторами мы можем захотеть создать список со всеми значениями из последовательности. Для этой ситуации нам нужна новая функция Tolist () Это принимает генератор и возвращает все значения из последовательности в качестве массива. Последовательность должна быть конечной.
function toList(sequence) {
const arr = [];
let value = sequence();
while (value !== undefined) {
arr.push(value);
value = sequence();
}
return arr;
}Давайте использовать его с предыдущим генератором.
const numbers = toList(sequence(10, 15)); //[10,11,12,13,14]
Оператор трубопровода
Трубопровод представляет собой серию преобразований данных, где вывод одного преобразования является входом следующего.
Оператор трубопровода |> Позволяет нам писать преобразования данных более выразительным образом. Оператор трубопровода обеспечивает синтаксический сахар над функционными вызовами с одним аргументом. Рассмотрим следующий код:
const shortText = shortenText(capitalize("this is a long text"));
function capitalize(text) {
return text.charAt(0).toUpperCase() + text.slice(1);
}
function shortenText(text) {
return text.substring(0, 8).trim();
}С помощью оператора трубопровода трансформация может быть написана так:
const shortText = "this is a long text" |> capitalize |> shortenText; //This is
На данный момент оператор трубопровода является экспериментальным. Вы можете попробовать его с помощью Babel:
- в
Package.jsonФайл Добавить Plugin Plugin:
{
"dependencies": {
"@babel/plugin-syntax-pipeline-operator": "7.2.0"
}
}- В
.babelrcФайл конфигурации Добавить:
{
"plugins": [["@babel/plugin-proposal-pipeline-operator", {
"proposal": "minimal" }]]
}Генераторы над коллекциями
В Упросить ваш код проще читать с функциональным программированием У меня был пример обработки списка тодос . Вот код:
function isPriorityTodo(task) {
return task.type === "RE" && !task.completed;
}
function toTodoView(task) {
return Object.freeze({ id: task.id, desc: task.desc });
}
const filteredTodos = todos.filter(isPriorityTodo).map(toTodoView);В этом примере Тодос Список проходит два преобразования. Первый фильтруемый список создается, то создается второй список с отображенными значениями.
С генераторами мы можем сделать два преобразования и создать только один список. Для этого нам нужен генератор Последовательность () Это дает следующее значение из коллекции.
function sequence(list) {
let index = 0;
return function() {
if (index < list.length) {
const result = list[index];
index += 1;
return result;
}
};
}Фильтр () и карта ()
Далее нам нужны два декоратора Фильтр () и карта () , что работа с функциональными генераторами.
Фильтр () принимает генератор и создает новый генератор, который возвращает только значения из последовательности, которая удовлетворяет функцию предиката.
карта () принимает генератор и создает новый генератор, который возвращает сопоставленную стоимость.
Вот реализации:
function filter(predicate) {
return function(sequence) {
return function filteredSequence() {
const value = sequence();
if (value !== undefined) {
if (predicate(value)) {
return value;
} else {
return filteredSequence();
}
}
};
};
}
function map(mapping) {
return function(sequence) {
return function() {
const value = sequence();
if (value !== undefined) {
return mapping(value);
}
};
};
}Я хотел бы использовать эти декораторы с помощью оператора трубопровода. Итак, вместо создания Фильтр (последовательность, предикат) {} С двумя параметрами я создал ее карриную версию, которая будет использоваться так: Фильтр (предикат) (последовательность) . Таким образом, он красиво работает с оператором трубопровода.
Теперь, когда у нас есть инструментарий, сделанный из последовательность , Фильтр , карта и Tolist Функции, для работы с генераторами над коллекциями, мы можем поставить их все в модуль ( »./Используйте" ). См. Ниже, чтобы как переписать предыдущий код, используя этот панель инструментов и оператор трубопровода:
import { sequence, filter, map, take, toList } from "./sequence";
const filteredTodos =
sequence(todos)
|> filter(isPriorityTodo)
|> map(toTodoView)
|> toList;Вот a Производительность теста Измерение разницы между использованием методов массива и использованием функциональных генераторов. Похоже, что подход с функциональными генераторами составляет 15-20% медленнее.
уменьшать()
Давайте возьмем еще один пример, который вычисляет цену фруктов из списка покупок.
function addPrice(totalPrice, line){
return totalPrice + (line.units * line.price);
}
function areFruits(line){
return line.type === "FRT";
}
let fruitsPrice = shoppingList.filter(areFruits).reduce(addPrice,0);Как видите, он требует от нас, чтобы сначала создать фильтрованный список, а затем вычисляет все общее в этом списке. Давайте переписываем вычисление с функциональными генераторами и избегайте создания отфильтрованного списка.
Нам нужна новая функция на панели инструментов: Уменьшить () Отказ Требуется генератор и уменьшает последовательность одному значению.
function reduce(accumulator, startValue) {
return function(sequence) {
let result = startValue;
let value = sequence();
while (value !== undefined) {
result = accumulator(result, value);
value = sequence();
}
return result;
};
}Уменьшить () имеет немедленное исполнение.
Вот код переписан генераторами:
import { sequence, filter, reduce } from "./sequence";
const fruitsPrice = sequence(shoppingList)
|> filter(areFruits)
|> reduce(addPrice, 0);брать()
Другим распространенным сценарием является принять только первый N элементы из последовательности. Для этого случая нам нужен новый декоратор взять () , это получает генератор и создает новый генератор, который возвращает только первый N элементы из последовательности.
function take(n) {
return function(sequence) {
let count = 0;
return function() {
if (count < n) {
count += 1;
return sequence();
}
};
};
}Опять же, это каррическая версия взять () Что нужно назвать так: взять (n) (последовательность) .
Вот как вы можете использовать взять () на бесконечной последовательности чисел:
import { sequence, toList, filter, take } from "./sequence";
function isEven(n) {
return n % 2 === 0;
}
const first3EvenNumbers = sequence()
|> filter(isEven)
|> take(3)
|> toList;
//[0, 2, 4]Я переделал предыдущий Производительность теста и использовать взять () обрабатывать только первые 100 предметов. Оказывается, версия с функциональными генераторами намного быстрее (например, в 170 раз быстрее).
let filteredTodos = todos .filter(isPriorityTodo) .slice(0, 100) .map(toTodoView); //320 ops/sec let filteredTodos = const filteredTodos = sequence(todos) |> filter(isPriorityTodo) |> map(toTodoView) |> take(100) |> toList; //54000 ops/sec
Пользовательские генераторы
Мы можем создать любой пользовательский генератор и использовать его с помощью Toolbox и оператором трубопровода. Давайте создадим пользовательский генератор FIBONACCI:
function fibonacciSequence() {
let a = 0;
let b = 1;
return function() {
const aResult = a;
a = b;
b = aResult + b;
return aResult;
};
}
const fibonacci = fibonacciSequence();
fibonacci();
fibonacci();
fibonacci();
fibonacci();
fibonacci();
const firstNumbers = fibonacciSequence()
|> take(10)
|> toList;
//[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]Заключение
Оператор трубопровода делает преобразование данных более выразительным.
Функциональные генераторы могут быть созданы над конечными или бесконечными последовательностями значений.
С генераторами мы можем сделать обработку списка без создания промежуточных списков на каждом шаге.
Вы можете проверить все образцы на CodeSandbox Отказ
Откройте для себя функциональный JavaScript был назван одним из Лучшие новые функциональные программирования книги BookAuthority Действительно
Для получения дополнительной информации о применении методов функциональных программиров в реакции посмотрите на Функциональный реагировать Отказ
Учить Функциональный реагировать в проекте, основанном на проекте, с Функциональная архитектура с реагированием и redux Отказ
Следуйте в Twitter
Оригинал: “https://www.freecodecamp.org/news/lets-experiment-with-functional-generators-and-the-pipeline-operator-in-javascript-520364f97448/”