Обновить
66
0.9
Вадим 老陆 Румянцев@vadimr

Разработчик аппаратно-программных комплексов

Отправить сообщение

Да, именно! Благодаря замороченному синтаксису здесь возникает ложная аналогия с потоком. Хотя никакого потока на самом деле нет, всё последовательно.

Вот смотрите, сколько механизмов вам пришлось использовать для объяснения работы генератора

def gen():
  while True:
    yield 1

в питоновском стиле:

  1. Определение функции gen само по себе.

  2. Замыкание функции gen, которое хранит состояние её внутренних переменных (в данном конкретном случае таких переменных нет, но в общем есть).

  3. Понимание того, что gen – это не обычная функция, а специальная функция-генератор (частный случай итератора), что фактически обеспечивается реализацией особенного метода связанного с ней объекта. В связи с этим (на минуточку) – ещё само понятие объекта.

  4. Оператор цикла.

  5. Оператор yield, служащий для временной приостановки выполнения генератора.

Я согласен, что при известной настойчивости можно понять и запомнить такое объяснение, и сам я его тоже понимаю, так как владею языком Python. Однако сравните это с реализацией точно того же самого генератора на языке Scheme, которую я уже приводил:

(define (gen)
  (cons 1 (delay (gen))))

;; cons добавляет единицу к последовательности

Здесь нет ничего, кроме определения функции и её рекурсивного отложенного вызова. Ни один из этих двух пунктов не введён в язык специально для генератора. Так какое объяснение одного и того же понятия генератора проще, первое или второе?

Отмечу, что и скомпилированный машинный код для Scheme скорее всего будет эффективнее по той же причине простоты операционной реализации.

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

Сам Карри, от фамилии которого пошло каррирование, использовал префиксную нотацию лямбда-исчисления, свободную от упомянутого вами недостатка.

Ну, во всяком случае, перегреваться вряд ли будет.

Рекурсия бывает прямая и косвенная. Вы оспариваете, что в результате действия оператора yield функция gen косвенно передаёт управление самой себе?

Тут надо более сложный генератор расписать, чтобы было более понятно. С внутренними переменными.

Там просто сохраняется фрейм отдельно и дальше продолжается выполнение с того места где остановились.

А чем это отличается от рекурсивного вызова и возврата?

В то время как я не слышал, чтобы в структуре управления "цикл" подразумевалось какое-то сохранение фреймов.

Посмотрите свежим взглядом, не замутнённым заморочками конкретного языка.

так то и рекурсия внутри превращается в цикл при оптимизации компилятором, но вы же это как-то игнорируете.

Не так. Рекурсия и цикл превращаются внутри в команды условного перехода, о чём Вы совершенно верно пишете ниже.

Генераторы - это закономерное расширение синтаксиса циклов, не ломающее стиль кода. В том время как рекурсия - альтернативная форма.

Очень избыточный в этом месте синтаксис языка Питон диктует такое ваше понимание. Однако для этого приходится привлечь массу дополнительных понятий и – самое главное – существенно переопределить само понятие цикла.

В то время как через рекурсию генератор выражается непосредственно. Но конкретно в Питоне, который сам по себе содержит многие полезные возможности функциональных языков программирования, именно так сделать было бы нельзя в первую очередь по той причине, что там по семантике подразумевается сохранение стека функциональных вызовов.

Однако неудобства конкретного языка для непосредственного выражения идеи не должны мешать нам рассматривать эту идею в чистом виде. Поэтому, конечно, связное изложение концепций программирования требует совместного использования разных языков.

Во что оно превращается внутри - да не важно вообще, наверняка там просто goto остаётся после всех возможных компиляций

Конечно.

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

И да, рекурсия не равна стеку.

Неформальное лидерство тоже возможно. Но оно приведёт либо к повышению, либо к конфликту и увольнению.

Не обязательно. Это зависит от того, насколько бюрократическая структура отдалена от реальной.

Например, возьмём первый попавшийся пример из жизни, в некоторой корпорации есть норматив по управляемости: отдел – не менее 20 человек (происходящий из изначально благой наивной идеи, что начальников должно быть мало). И хоть ты тресни, но если ты выполняешь работу вдевятнадцатиром, то начальником отдела тебе не быть. Поэтому, если хочешь быть начальником, то надо набирать бесполезных подчинённых (лучше, конечно, полезных, но это не так просто).

Я считаю, что это спор о терминах.

Сам я слово "поток" использую применительно к соответствующей структуре операционной системы или библиотеки среды программирования.

Тут надо главным образом определиться с тем, что называть потоком.

У вас gen вызывается внутри gen. У меня - нет

Можно сказать и так (чисто синтаксисически), но тем не менее это код одного и того же генератора.

Так я ж пишу, вы (вместе с авторами языка Python) просто замаскировали рекурсивный вызов возвратом очередного шага рекурсии в виде замыкания gen.

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

Если по-простому, и если вы допишете недостающий контекст, то здесь написано:

define (gen)
  (cons 1 (delay (gen)))

Вы можете объяснять это как-то более сложно (через мнимый цикл while, который не является циклом, труднопонимаемый оператор yield, возврат замыкания и неявный объект и его методы), но от природы вещей не уйти. Это простой как три копейки отложенный рекурсивный вызов.

Это синтаксически ошибочные операторы без контекста, который вы отрезали. А в контексте это возврат безымянного замыкания, которое содержит ленивый рекурсивный вызов (Y-комбинатор) и выполняет очередной шаг рекурсии.

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

Кстати, этот оператор while не является классическим циклом while в смысле структурного программирования, так как у него не структурный выход (и вход). У Дейкстры бы язва обострилась при взгляде на него.

Стек забьётся только если язык и транслятор не поддерживают оптимизацию хвостовой рекурсии.

Бесконечная рекурсия - вполне обычный приём в использующих рекурсию языках.

Генератор и является формой записи ленивой рекурсии специального вида. Не очень понятно, как их можно противопоставлять друг другу.

Мне даже стало интересно, как можно объяснить действие генератора, не привлекая понятия рекурсии.

Сложная рекурсия (например, функция Акермана) может не иметь эффективно вычислимого enumerable. А то и вообще вычислимого.

Генерации бесконечной последовательности. Как рекурсия может генерировать бесконечную последовательность если у нее есть результат? 

У рекурсии есть результат, но этот результат не обязательно жадно вычислять.

В то время как цикл или выполнился весь, или до дальнейших вычислений мы не дошли.

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

Кажется дело не в рекурсии как таковой, а в типе результата

(Не обязательно конечный) список, например.

Я довольно давно учился программированию :) Но вообще это известная практика обучения детей на базе Лого.

Код взаимосвязан с данными. По массиву (особенно фиксированной длины) яснее идти циклом, по списку (особенно мультисписку) – рекурсией.

Дело привычки. Если начинать изучение программирования с рекурсии, то она проще воспринимается (личный опыт). А если с goto, то будет проще восприниматься goto. Конечно, всё это если говорить об изоморфных случаях, то есть об одинаковой логике управления, выражаемой конструкцией while, как самой ограниченной из трёх.

while любят именно потому, что с его помощью сложнее наворотить запутанную логику.

Информация

В рейтинге
1 942-й
Откуда
Санкт-Петербург, Санкт-Петербург и область, Россия
Дата рождения
Зарегистрирован
Активность

Специализация

Менеджер проекта, Архитектор программного обеспечения
Ведущий