[JavaScript, Программирование] Эффектное программирование. Часть 1: итераторы и генераторы
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Javascript на данный момент является самым популярным языком программирования по версиям многих площадок (например Github). Является ли при этом он самым продвинутым или самым любимым языком? В нём отсутствуют конструкции, которые для других языков являются неотъемлемыми частями: обширная стандартная библиотека, иммутабильность, макросы. Но в нём есть одна деталь, которая не получает, на мой взгляд, достаточно внимания — генераторы.
Далее читателю предложена статья, которая, в случае положительного отклика, может перерасти в цикл. В случае успешного написания мной этого цикла, а Читателем его успешного освоения, про следующий код будет понятно не только то, что он делает, но и как устроен под капотом:
while (true) {
const data = yield getNextChunk(); // вызов асинхронной логики
const processed = processData(data);
try {
yield sendProcessedData(processed);
showOkResult();
} catch (err) {
showError();
}
}
Это первая, пилотная часть: Итераторы и Генераторы.
Итераторы
Итак, итератор — это интерфейс, предоставляющий последовательный доступ к данным.
Как видно, в определении ничего не сказано о структурах данных или памяти. Действительно, последовательность undefined-ов может быть представлена в виде итератора, при этом не занимать места в памяти.
Предлагаю читателю ответить на вопрос: является ли массив итератором?
Ответ
SPL
Является. Методы shift и pop отлично позволяют работать с массивом как с итератором.
Зачем же тогда нужны итераторы, если массив, одна из базовых структур языка, позволяет работать с данными и последовательно, и в произвольном порядке?
Представим, что нам нужен итератор, который реализует последовательность натуральных чисел. Или чисел Фибоначчи. Или любую другую бесконечную последовательность. В массиве сложно разместить бесконечную последовательность, понадобится механизм постепенного наполнения массива данными, а также изъятия старых данных, чтобы не заполнить всю память процесса. Это излишнее усложнение, которое несёт с собой дополнительную сложность реализации и поддержки, при том, что решение без массива может уместиться в несколько строчек:
const getNaturalRow = () => {
let current = 0;
return () => ++current;
};
Также итератором можно представить получение данных из внешнего канала, например websocket.
В javascript итератором является любой объект, у которого есть метод next(), который возвращает структуру с полями value — текущее значение итератора и done — флагом, указывающим на завершение последовательности (эта договорённость описана в стандарте языка ECMAScript). Такой объект реализует интерфейс Iterator. Перепишем прошлый пример в этом формате:
const getNaturalRow = () => ({
_current: 0,
next() { return {
value: ++this._current,
done: false,
}},
});
В javascript также есть интерфейс Iterable — это объект, который имеет метод @@iterator (данная константа доступна как Symbol.iterator), который возвращает итератор. Для объектов, реализующих такой интерфейс, доступен обход с помощью оператора for..of. Перепишем наш пример ещё раз, только в этот раз как реализацию Iterable:
const naturalRowIterator = {
[Symbol.iterator]: () => ({
_current: 0,
next() { return {
value: ++this._current,
done: this._current > 3,
}},
}),
}
for (num of naturalRowIterator) {
console.log(num);
}
// Вывод: 1, 2, 3
Как можно видеть, нам пришлось сделать так, чтобы флаг done в какой-то момент стал положительным, иначе бы цикл был бесконечным.
Генераторы
Следующим этапом эволюции итераторов стали генераторы. Они предоставляют синтаксический сахар, позволяющий возвращать значения итератора будто значение функции. Генератор — это функция (объявляется со звёздочкой: function*), возвращающая итератор. При этом итератор не возвращается явно, в функции лишь возвращаются значения итератора с помощью оператора yield. Когда функция заканчивает своё выполнение, итератор считается завершённым (результаты последующих вызовов метода next будут иметь флаг done равным true)
function* naturalRowGenerator() {
let current = 1;
while (current <= 3) {
yield current;
current++;
}
}
for (num of naturalRowGenerator()) {
console.log(num);
}
// Вывод: 1, 2, 3
Уже в этом простом примере невооружённым глазом виден главный нюанс генераторов: код внутри функции генератора не выполняется синхронно. Выполнение кода генератора происходит поэтапно, в результате вызовов next() у соответствующего итератора. Рассмотрим, как выполняется код генератора на прошлом примере. Специальным курсором будем отмечать, где остановилось выполнение генератора.
В момент вызова naturalRowGenerator создаётся итератор.
function* naturalRowGenerator() {
▷let current = 1;
while (current <= 3) {
yield current;
current++;
}
}
Далее, когда мы первые три раза вызываем метод next или, в нашем случае, проходим итерации цикла, курсор встаёт после оператора yield.
function* naturalRowGenerator() {
let current = 1;
while (current <= 3) {
yield current; ▷
current++;
}
}
И на все последующие вызовы next и после выхода из цикла генератор завершает своё выполнение и, результатами вызова next будет { value: undefined, done: true }
Передача параметров в итератор
Представим, что в наш итератор натуральных чисел нужно добавить возможность сбрасывать текущий счётчик и начинать отчёт с начала.
naturalRowIterator.next() // 1
naturalRowIterator.next() // 2
naturalRowIterator.next(true) // 1
naturalRowIterator.next() // 2
Понятно как обработать такой параметр в самописном итераторе, но как быть с генераторами?
Оказывается, генераторы поддерживают передачу параметров!
function* naturalRowGenerator() {
let current = 1;
while (true) {
const reset = yield current;
if (reset) {
current = 1;
} else {
current++;
}
}
}
Переданный параметр становится доступен как результат оператора yield. Попробуем добавить ясности с помощью подхода с курсором. В момент создания итератора ничего не поменялось. Далее следует первый вызов метода next():
function* naturalRowGenerator() {
let current = 1;
while (true) {
const reset = ▷yield current;
if (reset) {
current = 1;
} else {
current++;
}
}
}
Курсор замер на моменте возврата из оператора yield. При следующем вызове next, переданное в функцию значение установит значение переменной reset. Куда же попадёт значение, переданное в самый первый вызов next, ведь там же ещё не было вызова yield? Никуда! Растворится в просторах garbage collector-а. Если нужно передать какое-то начальное значение в генератор, то это можно сделать с помощью аргументов самого генератора. Пример:
function* naturalRowGenerator(start = 1) {
let current = start;
while (true) {
const reset = yield current;
if (reset) {
current = start;
} else {
current++;
}
}
}
const iterator = naturalRowGenerator(10);
iterator.next() // 10
iterator.next() // 11
iterator.next(true) // 10
Заключение
Мы рассмотрели концепцию итераторов и её реализацию в языке javascript. Также мы изучили генераторы — синтаксическую конструкцию для удобной реализации итераторов.
Хотя в данной статье я приводил примеры с числовыми последовательностями, итераторы в javascript позволяют решить намного больше задач. С помощью них можно представить любую последовательность данных и даже многие конечные автоматы. В следующей статье я хотел бы рассказать о том, как можно использовать генераторы для построения асинхронных процессов (coroutines, goroutines, csp и т. д.).
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, Беспроводные технологии, Разработка систем связи, Гаджеты, Научно-популярное] Определяем направление на аэропорт с помощью RTL-SDR и GNU Radio
- [Программирование микроконтроллеров, Производство и разработка электроники, DIY или Сделай сам, Звук] MIDI2USB – музыка нас связала
- [Программирование] Структурное против ООП программирование
- [Программирование, Dart, Flutter] Релиз Dart 2.10: на шаг ближе к null-safety (перевод)
- [Разработка веб-сайтов, JavaScript, ReactJS] Почему мы выбрали MobX, а не Redux, и как его использовать эффективнее
- [Программирование] Интеграция библиотеки на Swift в UE4
- [Программирование, Карьера в IT-индустрии] «Сферический Конь» в микросервисах, Тарантино, слежка и высокоградусный HR — треш-истории собеседований от разработчиков
- [Open source, JavaScript, Я пиарюсь] Javascript фреймворк разработки бизнес приложений
- [Open source, Rust, Компиляторы, Программирование, Системное программирование] Rust 1.47.0: const generics для массивов, LLVM 11, Control Flow Guard и сокращение трассировок (перевод)
- [Программирование, TypeScript] Chorda 2.0. Как я потерял и нашел API
Теги для поиска: #_javascript, #_programmirovanie (Программирование), #_iterators, #_generators, #_javascript, #_javascript, #_programmirovanie (
Программирование
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:43
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Javascript на данный момент является самым популярным языком программирования по версиям многих площадок (например Github). Является ли при этом он самым продвинутым или самым любимым языком? В нём отсутствуют конструкции, которые для других языков являются неотъемлемыми частями: обширная стандартная библиотека, иммутабильность, макросы. Но в нём есть одна деталь, которая не получает, на мой взгляд, достаточно внимания — генераторы. Далее читателю предложена статья, которая, в случае положительного отклика, может перерасти в цикл. В случае успешного написания мной этого цикла, а Читателем его успешного освоения, про следующий код будет понятно не только то, что он делает, но и как устроен под капотом: while (true) {
const data = yield getNextChunk(); // вызов асинхронной логики const processed = processData(data); try { yield sendProcessedData(processed); showOkResult(); } catch (err) { showError(); } } Это первая, пилотная часть: Итераторы и Генераторы. Итераторы Итак, итератор — это интерфейс, предоставляющий последовательный доступ к данным. Как видно, в определении ничего не сказано о структурах данных или памяти. Действительно, последовательность undefined-ов может быть представлена в виде итератора, при этом не занимать места в памяти. Предлагаю читателю ответить на вопрос: является ли массив итератором? ОтветSPLЯвляется. Методы shift и pop отлично позволяют работать с массивом как с итератором.
Зачем же тогда нужны итераторы, если массив, одна из базовых структур языка, позволяет работать с данными и последовательно, и в произвольном порядке? Представим, что нам нужен итератор, который реализует последовательность натуральных чисел. Или чисел Фибоначчи. Или любую другую бесконечную последовательность. В массиве сложно разместить бесконечную последовательность, понадобится механизм постепенного наполнения массива данными, а также изъятия старых данных, чтобы не заполнить всю память процесса. Это излишнее усложнение, которое несёт с собой дополнительную сложность реализации и поддержки, при том, что решение без массива может уместиться в несколько строчек: const getNaturalRow = () => {
let current = 0; return () => ++current; }; Также итератором можно представить получение данных из внешнего канала, например websocket. В javascript итератором является любой объект, у которого есть метод next(), который возвращает структуру с полями value — текущее значение итератора и done — флагом, указывающим на завершение последовательности (эта договорённость описана в стандарте языка ECMAScript). Такой объект реализует интерфейс Iterator. Перепишем прошлый пример в этом формате: const getNaturalRow = () => ({
_current: 0, next() { return { value: ++this._current, done: false, }}, }); В javascript также есть интерфейс Iterable — это объект, который имеет метод @@iterator (данная константа доступна как Symbol.iterator), который возвращает итератор. Для объектов, реализующих такой интерфейс, доступен обход с помощью оператора for..of. Перепишем наш пример ещё раз, только в этот раз как реализацию Iterable: const naturalRowIterator = {
[Symbol.iterator]: () => ({ _current: 0, next() { return { value: ++this._current, done: this._current > 3, }}, }), } for (num of naturalRowIterator) { console.log(num); } // Вывод: 1, 2, 3 Как можно видеть, нам пришлось сделать так, чтобы флаг done в какой-то момент стал положительным, иначе бы цикл был бесконечным. Генераторы Следующим этапом эволюции итераторов стали генераторы. Они предоставляют синтаксический сахар, позволяющий возвращать значения итератора будто значение функции. Генератор — это функция (объявляется со звёздочкой: function*), возвращающая итератор. При этом итератор не возвращается явно, в функции лишь возвращаются значения итератора с помощью оператора yield. Когда функция заканчивает своё выполнение, итератор считается завершённым (результаты последующих вызовов метода next будут иметь флаг done равным true) function* naturalRowGenerator() {
let current = 1; while (current <= 3) { yield current; current++; } } for (num of naturalRowGenerator()) { console.log(num); } // Вывод: 1, 2, 3 Уже в этом простом примере невооружённым глазом виден главный нюанс генераторов: код внутри функции генератора не выполняется синхронно. Выполнение кода генератора происходит поэтапно, в результате вызовов next() у соответствующего итератора. Рассмотрим, как выполняется код генератора на прошлом примере. Специальным курсором будем отмечать, где остановилось выполнение генератора. В момент вызова naturalRowGenerator создаётся итератор. function* naturalRowGenerator() {
▷let current = 1; while (current <= 3) { yield current; current++; } } Далее, когда мы первые три раза вызываем метод next или, в нашем случае, проходим итерации цикла, курсор встаёт после оператора yield. function* naturalRowGenerator() {
let current = 1; while (current <= 3) { yield current; ▷ current++; } } И на все последующие вызовы next и после выхода из цикла генератор завершает своё выполнение и, результатами вызова next будет { value: undefined, done: true } Передача параметров в итератор Представим, что в наш итератор натуральных чисел нужно добавить возможность сбрасывать текущий счётчик и начинать отчёт с начала. naturalRowIterator.next() // 1
naturalRowIterator.next() // 2 naturalRowIterator.next(true) // 1 naturalRowIterator.next() // 2 Понятно как обработать такой параметр в самописном итераторе, но как быть с генераторами? Оказывается, генераторы поддерживают передачу параметров! function* naturalRowGenerator() {
let current = 1; while (true) { const reset = yield current; if (reset) { current = 1; } else { current++; } } } Переданный параметр становится доступен как результат оператора yield. Попробуем добавить ясности с помощью подхода с курсором. В момент создания итератора ничего не поменялось. Далее следует первый вызов метода next(): function* naturalRowGenerator() {
let current = 1; while (true) { const reset = ▷yield current; if (reset) { current = 1; } else { current++; } } } Курсор замер на моменте возврата из оператора yield. При следующем вызове next, переданное в функцию значение установит значение переменной reset. Куда же попадёт значение, переданное в самый первый вызов next, ведь там же ещё не было вызова yield? Никуда! Растворится в просторах garbage collector-а. Если нужно передать какое-то начальное значение в генератор, то это можно сделать с помощью аргументов самого генератора. Пример: function* naturalRowGenerator(start = 1) {
let current = start; while (true) { const reset = yield current; if (reset) { current = start; } else { current++; } } } const iterator = naturalRowGenerator(10); iterator.next() // 10 iterator.next() // 11 iterator.next(true) // 10 Заключение Мы рассмотрели концепцию итераторов и её реализацию в языке javascript. Также мы изучили генераторы — синтаксическую конструкцию для удобной реализации итераторов. Хотя в данной статье я приводил примеры с числовыми последовательностями, итераторы в javascript позволяют решить намного больше задач. С помощью них можно представить любую последовательность данных и даже многие конечные автоматы. В следующей статье я хотел бы рассказать о том, как можно использовать генераторы для построения асинхронных процессов (coroutines, goroutines, csp и т. д.). =========== Источник: habr.com =========== Похожие новости:
Программирование ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:43
Часовой пояс: UTC + 5