Русский
Русский
English
Статистика
Реклама

Из песочницы Эффектное программирование. Часть 1 итераторы и генераторы

Javascript на данный момент является самым популярным языком программирования по версиям многих площадок (например Github). Является ли при этом он самым продвинутым или самым любимым языком? В нём отсутствуют конструкции, которые для других языков являются неотъемлемыми частями: обширная стандартная библиотека, иммутабильность, макросы. Но в нём есть одна деталь, которая не получает, на мой взгляд, достаточно внимания генераторы.

Далее читателю предложена статья, которая, в случае положительного отклика, может перерасти в цикл. В случае успешного написания мной этого цикла, а Читателем его успешного освоения, про следующий код будет понятно не только то, что он делает, но и как устроен под капотом:

while (true) {    const data = yield getNextChunk(); // вызов асинхронной логики    const processed = processData(data);    try {        yield sendProcessedData(processed);        showOkResult();    } catch (err) {        showError();    }}

Это первая, пилотная часть: Итераторы и Генераторы.

Итераторы


Итак, итератор это интерфейс, предоставляющий последовательный доступ к данным.

Как видно, в определении ничего не сказано о структурах данных или памяти. Действительно, последовательность undefined-ов может быть представлена в виде итератора, при этом не занимать места в памяти.

Предлагаю читателю ответить на вопрос: является ли массив итератором?

Ответ
Является. Методы 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() // 1naturalRowIterator.next() // 2naturalRowIterator.next(true) // 1naturalRowIterator.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() // 10iterator.next() // 11iterator.next(true) // 10

Заключение


Мы рассмотрели концепцию итераторов и её реализацию в языке javascript. Также мы изучили генераторы синтаксическую конструкцию для удобной реализации итераторов.

Хотя в данной статье я приводил примеры с числовыми последовательностями, итераторы в javascript позволяют решить намного больше задач. С помощью них можно представить любую последовательность данных и даже многие конечные автоматы. В следующей статье я хотел бы рассказать о том, как можно использовать генераторы для построения асинхронных процессов (coroutines, goroutines, csp и т. д.).
Источник: habr.com
К списку статей
Опубликовано: 10.10.2020 16:22:36
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Javascript

Программирование

Iterators

Generators

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru