Подтвердить что ты не робот

Генераторы Python на разных языках

Как вы эмулируете генераторы стиля Python на вашем любимом языке? Я нашел этот в схеме. Должно быть интересно увидеть другие реализации, особенно на тех языках, которые не имеют первоклассных продолжений.

4b9b3361

Ответ 1

Вот пример в С++, который имитирует генераторы с использованием волокон:

Итератор возврата урожая для собственных С++ с использованием волокон

Итератор "yield return" является языковой функции, которая была создана для одна причина: простота. это как правило, намного проще итерации через весь коллекцию, сохраняя все контекст, необходимый для локальных переменных, вместо того, чтобы создавать сложные, пользовательский объект итератора, который хранит его состояние через последующий поиск операции.

Существуют также примитивные подпрограммы C setjmp, longjmp для достижения аналогичных результатов.
(Lua сопрограммы реализованы с помощью вышеуказанного метода)

Ответ 2

Я бы вообще не использовал выход в Lisp/Scheme.

'yield' требует какого-то совместного подхода или продолжения на языке. Многие виды использования урожая могут быть реализованы более простым функциональным способом.

YIELD в основном связан с известным оператором COME-FROM.;-) Здесь вызов в некотором месте может привести к разным местам в какой-то другой рутине, в зависимости от контекста выполнения. Таким образом, рутина внезапно имеет несколько точек входа, порядок которых определяется во время выполнения. Для простых целей это может быть хорошо, но я бы сказал, что для более сложного кода рассуждения о коде будут усложняться.

Возьмите пример схемы, связанный в вопросе:

(define/y (step) 
  (yield 1)
  (yield 2)
  (yield 3)
  'finished)

(list (step) (step) (step))

Вызов (шаг) несколько раз возвращает разные значения.

Я бы просто создал закрытие:

(define step
  (let ((state '(1 2 3 finished)))
    (lambda ()
      (pop state))))

Это прерывает функцию с выходом на две разные вещи: переменную, которая несет состояние и простую функцию, которая изменяет состояние. Состояние уже неявно закодировано в последовательности выполнения.

 (list (step) (step) (step))))

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

Сравните это с генераторами из общей библиотеки Lisp SERIES:

(let ((x (generator (scan '(1 2 3 finished)))))
  (list (next-in x)
        (next-in x)
        (next-in x)))

Если мы рассмотрим этот пример Python из другого ответа

def simpleRange(n):
    for i in xrange(n):
        yield i

for n in simpleRange(5):
     print(n)

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

Ответ 3

В JavaScript 1.7+ мне обычно нужно добавить несколько скобок и скобок. Все остальное примерно одинаково. JavaScript 1.7 представил среди прочего питонические генераторы и итераторы.

Выражения генератора:

# Python
(x + 1 for x in y if x > 100)

// JavaScript 1.8+
(x + 1 for (x in y) if (x > 100))

Генераторы

# Python
def simpleRange(n):
    for i in xrange(n):
        yield i

for n in simpleRange(5):
     print(n)


// JavaScript 1.7+
function simpleRange(n) {
    for (let i = 0; i < n; i++)
        yield i;
}

for (n in simpleRange(5))
    print(n);

Учет List/Array

# Python
[x + 1 for x in y if x > 100]

// JavaScript 1.7+
[x + 1 for (x in y) if (x > 100)]

Ответ 4

С++, используя Generators

Объявление простого генератора диапазона:

$generator(range)
{
   int i;
   int _max;
   int _min;

   range(int minv, int maxv):_max(maxv),_min(minv) {}

   $emit(int) // will emit int values. Start of body of the generator.
      for (i = _min; i <= _max; ++i)
         $yield(i); 
   $stop;
};

Использование:

range r10(1,10);
for(int n; r10(n);) 
   printf("%d\n",n);

Выведет

1
2 
...
10

Ответ 5

В ответ @dmitry_vk о Common Lisp Я бы добавил, что в Lisp на самом деле генераторы действительно не нужны. Их варианты использования полностью охватываются различными приложениями замыканий, специальных переменных и макросов - без дополнительных концептуальных накладных расходов на изучение новой конструкции.

Иногда даже встроенные конструкции будут работать. Давайте посмотрим на пример из вики-страницы Python:

# add squares less than 100
from itertools import count, takewhile

sum = 0
square = (i*i for i in count())
bounded_squares = takewhile(lambda x: x < 100, square)
for i in bounded_squares:
   sum += i

Используя loop, он может быть реализован гораздо более простым способом:

CL-USER> (loop :for i :from 0 :while (< i 100) :sum (expt i 2))
328350

Поскольку loop намного более универсален, чем Python for, здесь нет необходимости вводить специальный синтаксис.

Рассмотрим другой вариант использования - итерацию по некоторому произвольному дереву. Предположим, что дерево представлено node, указывая на их children.

(defstruct node
  data
  children)

Мы можем пройти через любое дерево/поддерево с довольно маленьким и простым макросом.

(defmacro dotree ((var root &optional result-form) &body body)
  `(block nil
     (labels ((traverse (node)
                (let ((,var node))
                  ,@body
                  (dolist (child (children node))
                    (traverse child))
                  ,result-form)))
       (when-it ,root
         (traverse it)))))

(Предупреждение: для большей ясности я не использовал gensym в нем, но вам нужно).

Здесь пример его использования - получение списка всех листовых узлов:

(let (rez)
  (dotree (node tree (reverse rez))
    (when (leafp node)
      (push node rez))))

Это выглядит и работает так же, как стандартный макрос dolist. И, как и в случае с dolist, вы можете остановить итерацию в любое время, вызвав return.

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

P.S. Вы также можете взглянуть на библиотеку SERIES Lisp, которая реализовала аналогичную концепцию для генераторов в 90-х годах. Или CLAZY - с конца 2000-х годов.

Ответ 6

Monads можно использовать для представления генераторов (даже если семантика немного отличается).

Таким образом, здесь можно использовать любой язык, позволяющий определять монадические операции в специальном синтаксисе.

  • VB.NET/C# (Linq - но С# уже получил yield return)
  • Scala (для понимания)
  • Haskell (do-notation)
  • F #/OCaml (Вычисление выражений/Выполнение)

Ruby может эмулировать генераторы через встроенные возможности продолжения.

Ответ 7

Общий Lisp, хотя и не имеет собственных продолжений, позволяет создавать разграниченные продолжения с использованием трансформаторов CPS, таких как cl-cont. Таким образом, генераторы в Common Lisp могут быть записаны почти так же, как генераторы Схемы.

Кстати, генераторы на основе продолжения имеют одну черту, которой не хватает генераторам Python и С#: yield можно вызвать в динамической степени вызова функции генератора. Генераторы Python и С# позволяют размещать yield только внутри тела генератора.

Ответ 8

Ruby:

Функция генератора:

def simple_range(n)
    Enumerator.new do |y|    
        (0..n).each { |v| y.yield(v) }
    end
end

Ответ 9

Обычно yield избыточен на языках, которые имеют функции первого класса. Например, в TIScript вы можете делать генераторы следующим образом:

Генератор. Обратите внимание: он возвращает внутреннюю функцию.

function range( from, to )
{
  var idx = from - 1;
  return function() { if( ++idx <= to ) return idx; } // yields value on call
}

И его использование:

for( var item in range(12,24) )
  stdout << item << " ";

for(elem in source) в TIScript несколько отличается от JS. Если источником является функция, он получает вызов, а его возвращаемое значение присваивается elem, пока функция не вернет void (значение по умолчанию для пустой функции).