По факту это рецензия. На статью, которая показалась мне настолько показательной, что я решил написать на неё развернутый отзыв. Причём показательна она сразу в двух плоскостях: во‑первых, идея о том, что генераторы экономят память, всё ещё находит своих приверженцев; во‑вторых, тема превращения человека в бессловесный придаток бездушной машины не стояла так остро со времени выхода на экраны фильма «Матрица».
Про генераторы
Сначала немного к истории вопроса. Генераторы — очень прикольная штука, которая имеет множество применений. К примеру, в PHP фреймворке Symfony генераторы используются для решения самых разнообразных задач, от управления ходом выполнения программы до добавления элементов в массив на лету уже после того, как его начали перебирать (причём я бы сказал, что использование генераторов в Symfony выглядит уже как натуральная обсессия).
Вдохновившись примером Symfony, мы можем написать свой простенький генератор, который добавляет заголовок к таблице с данными, причём вызов будет выглядеть совершенно прозрачно,
мы просто перебираем "массив", но при этом первой строкой выведется заголовок
class Table {
...
function withHeader() {
yield $this->getHeader();
foreach ($this->getData() as $row) {
yield $row;
}
}
}
foreach ((new Table)->withHeader() as $row) {
echo implode (",",$row),"\n";
}
Что формирующий, что вызывающий код получился очень простым и наглядным, без изменения исходного массива, как того бы потребовал традиционный подход. И это только один из множества вариантов креативного использования генераторов. А ведь с их помощью можно делать самые разные трюки, вплоть до реализации кооперативной многозадачности с корутинами!
Как можно заметить, во всех этих примерах мы ни разу не упоминали экономию памяти. Но в массовом сознании генераторы по какой‑то причине твёрдо ассоциируются именно с «экономией памяти». Что неизбежно отражается на информации, которую вам выдаст Chat GPT или подобная генеративная модель.
На этой теме, хочешь‑не хочешь, придётся остановиться подробно. Одной из фишек генераторов является то, что они могут легко превратить любой цикл в foreach
. Что, собственно, и показывают многочисленные примеры кода, «экономящего память»: берём цикл while
или for
, оборачиваем его в генератор, и гордо заявляем, что он‑то и сэкономил нам всю память! В то время как на самом деле за экономию памяти отвечает старый как мир принцип «читать строки по одной», а генератор — в этой, одной из своих многочисленных ипостасей — всего лишь позволяет замаскировать исходный цикл под перебор массива. Отсюда мы можем сделать два простых вывода:
Сам по себе генератор память не экономит. Ну это же очевидно. Это не Гарри Поттер со своей палочкой, который куда‑то сначала спрячет все данные, а потом оттуда же достанет. Память экономит принцип «чтение по одному».
Что нам генератор позволяет — это не писать всю логику внутри цикла получения данных, а позволяет взять этот цикл, и перенести его в функцию, которая ожидает массив. То есть генератор — в этой своей ипостаси — позволяет писать более чистый код.
Говорить об экономии памяти можно только в том случае, если по какой‑то причине мы обязаны использовать foreach
— то есть в случае, когда у нас есть готовый код, который на вход принимает массив. Но в этом случае надо всегда делать такую оговорку: мол, память мы сэкономили не потому что генератор такой волшебный, а потому у нас уже был код, работающий с массивом, и мы подменили массив генератором. То есть (обещаю, я повторяю это в самый последний раз!) — с помощью генератора мы получили не возможность экономить память, а возможность делать это красиво!
Кто‑то скажет, «ну что за ерунда, подумаешь какая разница — все равно ведь память сэкономили»! Но если годы, проведенные в IT, меня чему‑то и научили — то это важности корректных формулировок. Даже самые благие намерения, сформулированные по‑дурацки, приводят к реальным проблемам. Взять, к примеру, печально известную максиму «вы должны всегда экранировать пользовательский ввод». Так и здесь — если утверждается, что генераторы экономят память, то найдётся гений, который засунет внутрь генератора массив, и гордо заявит, что его решение «Built with generators to process massive files (CSV, JSON, XML) without running out of memory.» Надо признать, впрочем, что CSV и JSON он всё‑таки читает потоком, а не задалось у него только с XML. Но главное — этот немного утрированный пример показывает, что в конечном итоге не генератор экономит память, а человек. Этот вывод, кстати сказать, неожиданно сближает нас с темой генеративного ИИ, но об этом чуть ниже.
Про статью
Некоторое время назад на Хабре была опубликована статья Ленивые вычисления в PHP: как генераторы и итераторы экономят память и ускоряют код, которая представляет собой всё те же рассуждения на тему «генераторы экономят память». «Реальный кейс из продакшена» — это всё та же подмена понятий (экономия за счёт чтения с пагинацией, но все лавры — генератору), причём сам код примера очень похож на умозрительные примеры из интернета.
В общем, ничего примечательного, если бы не реакция автора на критику. После моего критического комментария, автор резко сменил нарратив. И написал ровно то, что на самом деле должно было быть в статье! Его комментарий — просто квинтэссенция правильного представления о генераторах (снова оговорюсь, что речь идёт об одном из множества вариантов применения), в очень чётких формулировках:
«Если цель — именно экономия памяти, она и так достигается стримингом.»
«Фишка генераторов — унификация интерфейса»
«когда важно разделить источник данных и обработку»
А ведь это именно то, что делает LLM, если натыкать её мордой в галлюцинации (чем, кстати, всегда напоминает мне незадачливого прапорщика из анекдота «А крокодилы летают?»).
Ну и, разумеется, все заметили непременное «Спасибо за подробный разбор ?», с обязательным эмодзи, хе хе. Словом, это выглядит, как ответ LLM на критику, который автор скопипастил в свой комментарий. Но сам из этой критики, увы, не понял примерно ничего. Поскольку иначе понял бы и бессмысленность исходной статьи.
Кроме того, в статье есть очень странный пассаж про итераторы:
Генераторы — это быстро и просто. Но иногда нужно больше контроля: хранить состояние, управлять ключами или даже динамически менять источник данных.
Тогда в бой идёт Iterator API.
Здесь есть две странности. Во‑первых, все эти достоинства итераторов в равной мере относятся и к генераторам (и это как раз в духе генеративного ИИ, которому что итераторы, что генераторы — без разницы, лишь бы слова красиво друг за другом шли). Во‑вторых, тему итераторов автор дальше не развивал, оставив её висеть в воздухе.
Но после моей критики автор начал интенсивно править статью, что привело к совершенному уже анекдоту: он добавил (причём весьма быстро, в течение пары часов), кучу примеров, в числе которых один, реализующий функциональность, ранее приписываемую итераторам,
но сделал это с помощью генератора ?

После следующего замечания он эти примеры убрал, но к тому моменту я сохранил копию статьи (наивно вообразив, что автор удалит её, устыдившись столь серьёзного фиаско). В итоге статья осталась почти в исходном виде, с кучей заявлений о том, как генераторы отлично экономят память на пустом месте.
Про генеративный ИИ
И здесь мы возвращаемся к теме генеративного ИИ, вокруг которого не перестают ломаться копья. При том что воюющих, на мой взгляд, очень легко примирить, выделив два основных паттерна использования:
В руках специалиста, ИИ — это очень мощный инструмент, который предоставляет помощь в решении множества разных задач, от поиска ошибок до выполнения черновой или рутинной работы. При этом важным условием является то, что человек должен быть способен выполнить всю эту работу и сам! Просто с использованием ИИ получается быстрее.
При этом ИИ часто пытаются использовать и дилетанты, слепо копируя полученную информацию, не понимая её смысла, не видя противоречий и галлюцинаций. В этом случае результат немного предсказуем.
И всё сразу становится на свои места. ИИ — просто ещё один инструмент, повышающий производительность труда, но не заменяющий всего работника целиком.
Кстати, в связи с первым вариантом, мне вдруг вспомнился замечательный рассказ «Лена». И подумалось, что часть описанных там ужасов уже не актуальна. Во всяком случае, решение рутинных задач («визуальный анализ, пилотирование транспортного средства или беспилотные операции на заводе/складе/кухне»), для чего в рассказе привлекался электронный образ человеческой личности, уже становится неактуальным: мы подошли к решению этой проблемы с другого конца, с помощью чисто искусственного интеллекта.
Говоря же о втором, надо сделать оговорку. Для некритичных участков использование ИИ неспециалистом может быть оправдано. Например, генерация обязательной картинки к публикации на Хабре или проверка правописания в тексте на неродном языке. Даже если ИИ и допустит какой‑то промах, это не скажется на основной задаче (впрочем, тут надо следить за тем, чтобы ИИ не оставлял своих характерных следов — иначе текст, только исправленный ИИ, внешне будет выглядеть неотличимо от целиком им написанного).
Но всё это, повторюсь, будет работать только для некритичных участков, оформления. Если же дилетант запросит у ИИ решение основной задачи — написание кода, формулировку основной мысли статьи, и так далее — то в таких случаях положительного результата можно добиться разве что случайно. Но больше всего меня в этой истории огорчает то унизительное положение, в которое добровольно ставит себя человек, бездумно повторяя за не слишком‑то умной железкой, заведомо ставя себя ниже её. Неужели самому не противно?