WeLabel

Ещё раз о генераторах (и немного о генеративных моделях)

2025-09-01

По факту это рецензия. На статью, которая показалась мне настолько показательной, что я решил написать на неё развернутый отзыв. Причём показательна она сразу в двух плоскостях: во-первых, идея о том, что генераторы экономят память, всё ещё находит своих приверженцев; во-вторых, тема превращения человека в бессловесный придаток бездушной машины не стояла так остро со времени выхода на экраны фильма "Матрица".

Про генераторы

Сначала немного к истории вопроса. Генераторы - очень прикольная штука, которая имеет множество применений. К примеру, в 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, оборачиваем его в генератор, и гордо заявляем, что он-то и сэкономил нам всю память! В то время как на самом деле за экономию памяти отвечает старый как мир принцип "читать строки по одной", а генератор - в этой, одной из своих многочисленных ипостасей - всего лишь позволяет замаскировать исходный цикл под перебор массива. Отсюда мы можем сделать два простых вывода:

  1. Сам по себе генератор память не экономит. Ну это же очевидно. Это не Гарри Поттер со своей палочкой, который куда-то сначала спрячет все данные, а потом оттуда же достанет. Память экономит принцип "чтение по одному".

  2. Что нам генератор позволяет - это не писать всю логику внутри цикла получения данных, а позволяет взять этот цикл, и перенести его в функцию, которая ожидает массив. То есть генератор - в этой своей ипостаси - позволяет писать более чистый код.

Говорить об экономии памяти можно только в том случае, если по какой-то причине мы обязаны использовать 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.

Здесь есть две странности. Во-первых, все эти достоинства итераторов в равной мере относятся и к генераторам (и это как раз в духе генеративного ИИ, которому что итераторы, что генераторы - без разницы, лишь бы слова красиво друг за другом шли). Во-вторых, тему итераторов автор дальше не развивал, оставив её висеть в воздухе.

Но после моей критики автор начал интенсивно править статью, что привело к совершенному уже анекдоту: он добавил (причём весьма быстро, в течение пары часов), кучу примеров, в числе которых один, реализующий функциональность, ранее приписываемую итераторам,

но сделал это с помощью генератора 😂

После следующего замечания он эти примеры убрал, но к тому моменту я сохранил копию статьи (наивно вообразив, что автор удалит её, устыдившись столь серьёзного фиаско). В итоге статья осталась почти в исходном виде, с кучей заявлений о том, как генераторы отлично экономят память на пустом месте.

Про генеративный ИИ

И здесь мы возвращаемся к теме генеративного ИИ, вокруг которого не перестают ломаться копья. При том что воюющих, на мой взгляд, очень легко примирить, выделив два основных паттерна использования:

  1. В руках специалиста, ИИ - это очень мощный инструмент, который предоставляет помощь в решении множества разных задач, от поиска ошибок до выполнения черновой или рутинной работы. При этом важным условием является то, что человек должен быть способен выполнить всю эту работу и сам! Просто с использованием ИИ получается быстрее.

  2. При этом ИИ часто пытаются использовать и дилетанты, слепо копируя полученную информацию, не понимая её смысла, не видя противоречий и галлюцинаций. В этом случае результат немного предсказуем.

И всё сразу становится на свои места. ИИ - просто ещё один инструмент, повышающий производительность труда, но не заменяющий всего работника целиком.

Кстати, в связи с первым вариантом, мне вдруг вспомнился замечательный рассказ "Лена". И подумалось, что часть описанных там ужасов уже не актуальна. Во всяком случае, решение рутинных задач ("визуальный анализ, пилотирование транспортного средства или беспилотные операции на заводе/складе/кухне"), для чего в рассказе привлекался опенсорсный электронный образ человеческой личности, уже становится неактуальным: мы подошли к решению этой проблемы с другого конца, с помощью чисто искусственного интеллекта.

Говоря же о втором, надо сделать оговорку. Для некритичных участков использование ИИ неспециалистом может быть оправдано. Например, генерация обязательной картинки к публикации на Хабре или проверка правописания в тексте на неродном языке. Даже если ИИ и допустит какой-то промах, это не скажется на основной задаче (впрочем, тут надо следить за тем, чтобы ИИ не оставлял своих характерных следов - иначе текст, только исправленный ИИ, внешне будет выглядеть неотличимо от целиком им написанного).

Но всё это, повторюсь, будет работать только для некритичных участков, оформления. Если же дилетант запросит у ИИ решение основной задачи - написание кода, формулировку основной мысли статьи, и так далее - то в таких случаях положительного результата можно добиться разве что случайно. Но больше всего меня в этой истории огорчает то унизительное положение, в которое добровольно ставит себя человек, бездумно повторяя за не слишком-то умной железкой, заведомо ставя себя ниже её. Неужели самому не противно?

Автор: FanatPHP
Теги:
искусственный интеллектгенераторyieldэкономия памятигенеративный ии