11.6 Генераторы

Генератор — это функция, которая производит потенциально бесконечный поток значений. Каждый раз, когда функция производит значение, она приостанавливает свою работу и ожидает, пока вызывающая сторона запросит следующее значение.

Макрос: iter-defun name args [doc] [declare] [interactive] body…

iter-defun определяет генераторную функцию. Генераторная функция имеет ту же сигнатуру, что и обычная функция, но работает по-другому. Вместо выполнения body при вызове генераторная функция возвращает объект итератора. Этот итератор выполняет body для генерации значений, выдавая значение и приостанавливаясь в месте, где встречается iter-yield или iter-yield-from. Когда body завершается нормально, iter-next сигнализирует iter-end-of-sequence с результатом body в качестве данных условия.

Любой код Lisp допустим внутри body, но iter-yield и iter-yield-from не могут встречаться в формах unwind-protect.

Макрос: iter-lambda args [doc] [interactive] body…

iter-lambda создает неономерованную генераторную функцию, которая работает точно так же, как генераторная функция, созданная с помощью iter-defun.

Макрос: iter-yield value

Когда он встречается внутри генераторной функции, iter-yield указывает на то, что текущий итератор должен приостановиться и вернуть value из iter-next. iter-yield эквивалентен параметру value в следующем вызове iter-next.

Макрос: iter-yield-from iterator

iter-yield-from выдает все значения, которые производит iterator, и эквивалентен значению, которое возвращает генераторная функция iterator при нормальном завершении. Пока он контролирует выполнение, iterator получает значения, отправленные ему с помощью iter-next.

Чтобы использовать генераторную функцию, сначала вызовите ее обычным образом, получив объект итератора. Итератор — это конкретный экземпляр генератора. Затем используйте iter-next для извлечения значений из этого итератора. Когда больше значений для извлечения из итератора нет, iter-next вызывает условие iter-end-of-sequence с конечным значением итератора.

Важно отметить, что тело генераторной функции выполняется только внутри вызовов iter-next. Вызов функции, определенной с помощью iter-defun, создает итератор; вы должны управлять этим итератором с помощью iter-next, чтобы произошло что-либо интересное. Каждый вызов генераторной функции создает разный итератор, каждый со своим собственным состоянием.

Функция: iter-next iterator &optional value

Извлекает следующее значение из iterator. Если больше значений генерировать нет (потому что генераторная функция iterator вернулась), iter-next сигнализирует условием iter-end-of-sequence; значение данных, связанное с этим условием, — это значение, с которым генераторная функция iterator вернулась.

value передается в итератор и становится значением, к которому эквивалентно iter-yield. value игнорируется для первого вызова iter-next для данного итератора, поскольку в начале генераторной функции iterator генераторная функция не выполняет никакую форму iter-yield.

Функция: iter-close iterator

Если iterator приостановлен внутри bodyform unwind-protect и становится недостижимым, Emacs в конечном итоге выполнит обработчики отмены после прохода сборки мусора. (Обратите внимание, что iter-yield недопустим внутри unwindforms unwind-protect.) Чтобы гарантировать, что эти обработчики будут выполнены до этого, используйте iter-close.

Предоставлен ряд удобных функций, чтобы облегчить работу с итераторами:

Макрос: iter-do (var iterator) body …

Выполняет body, привязывая var к каждому значению, которое производит iterator.

В модуле Common Lisp также есть функции для работы с итераторами. См. Loop Facility в Common Lisp Extensions.

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

(require 'generator)
(iter-defun my-iter (x)
  (iter-yield (1+ (iter-yield (1+ x))))
   ;; Return normally
  -1)

(let* ((iter (my-iter 5))
       (iter2 (my-iter 0)))
  ;; Prints 6
  (print (iter-next iter))
  ;; Prints 9
  (print (iter-next iter 8))
  ;; Prints 1; iter and iter2 have distinct states
  (print (iter-next iter2 nil))

  ;; We expect the iter sequence to end now
  (condition-case x
      (iter-next iter)
    (iter-end-of-sequence
      ;; Prints -1, which my-iter returned normally
      (print (cdr x)))))