Generadores de python en varios idiomas

¿Cómo emulas los generadores de estilo Python en tu idioma favorito? Encontré este en el esquema. Debe ser interesante ver otras implementaciones, especialmente en aquellos idiomas que no tienen continuaciones de primera clase.

Aquí hay un ejemplo en C ++ que simula generadores usando fibras:

Iterador de retorno de rendimiento para C ++ nativo utilizando fibras

El iterador “retorno de rendimiento” es una característica del lenguaje que se creó por una razón: la simplicidad. En general, es mucho más fácil iterar en toda la colección, almacenando todo el contexto necesario en las variables locales, en lugar de crear un objeto iterador personalizado y complicado que almacena su estado en las operaciones de recuperación posteriores.

También existen las rutinas C primitivas setjmp, longjmp para lograr resultados similares.
(Lua coroutines se implementan con el método anterior)

No usaría el rendimiento en absoluto en Lisp / Scheme.

El ‘rendimiento’ requiere algún tipo de co-rutina o facilidad de continuación en el idioma. Muchos usos del rendimiento pueden implementarse de una manera funcional más simple.

YIELD está básicamente relacionado con el famoso operador COME-FROM. 😉 Aquí, una llamada en algún lugar puede llevar a diferentes lugares en alguna otra rutina, dependiendo de su contexto de ejecución. Entonces, una rutina repentinamente tiene múltiples puntos de entrada cuyo orden se determina en tiempo de ejecución. Para usos simples, esto puede estar bien, pero yo diría que para un código más complejo, el razonamiento sobre el código sería más difícil.

Tomemos el ejemplo de esquema vinculado en la pregunta:

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

Llamar (paso) varias veces devuelve valores diferentes a continuación.

Yo solo crearía un cierre:

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

Esto rompe la función anterior con un rendimiento en dos cosas diferentes: una variable que transporta el estado y una función simple que cambia el estado. El estado ya no está codificado implícitamente en la secuencia de ejecución.

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

Se pueden imaginar soluciones similares para otros usos del rendimiento.

Compare eso con los generadores de la biblioteca SERIE Common Lisp :

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

Si miramos este ejemplo de Python de otra respuesta

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

Podemos ver que duplica la estructura de control. Tanto el lugar de llamada como el generador utilizan una estructura de control de iteración FOR. Usando cierres, podemos deshacernos del uso de estructuras de control dentro del generador, solo proporcionando el código de transición de estado.

En JavaScript 1.7+ usualmente solo tengo que agregar algunos paréntesis y paréntesis. Todo lo demás es lo mismo. JavaScript 1.7 introdujo generadores e iteradores pythonicos entre otras cosas.

Expresiones del generador:

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

Generadores

 # 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); 

Lista / comprensión de arrays

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

C ++, utilizando generadores

Declaración del generador de rango simple:

 $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; }; 

Su uso:

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

Saldrá

 1 2 ... 10 

A la respuesta de @dmitry_vk sobre Common Lisp, agregaría que en Lisp, en realidad, los generadores no son realmente necesarios. Sus casos de uso están completamente cubiertos por diferentes aplicaciones de cierres, variables especiales y macros, sin la sobrecarga conceptual adicional de aprender un nuevo constructo.

A veces, incluso las construcciones integradas funcionarán. Veamos el ejemplo de la wiki de 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 

Usando loop se puede implementar de una manera mucho más directa:

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

Como el loop es mucho más versátil, Python's, for no es necesario introducir una syntax especial aquí.

Consideremos otro caso de uso: iteración sobre un árbol personalizado. Supongamos que el árbol está representado por node apuntan a sus children .

 (defstruct node data children) 

Podemos caminar sobre cualquier árbol / subárbol con una macro bastante pequeña y simple.

 (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))))) 

(Advertencia: para mayor claridad, no gensym , pero debería).

Este es el ejemplo de su uso: obtener una lista de todos los nodos de hoja:

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

Esto se ve y funciona igual que la macro dolist estándar. Y, como con dolist , puede detener la iteración en cualquier momento, llamando a return .

En general, todavía no veo un ejemplo práctico de uso del generador que no pueda implementarse en Lisp de una manera menos compleja.

También puede echar un vistazo a la biblioteca SERIES Lisp, que implementó un concepto similar a los generadores en los años 90. O CLAZY - desde finales de los 2000's.

Las mónadas se pueden usar para representar generadores (incluso si la semántica es un poco diferente).

Por lo tanto, cualquier lenguaje que nos permita definir operaciones monádicas dentro de una syntax especial puede usarse aquí.

  • VB.NET/C# (Linq – pero C # ya obtuvo yield return )
  • Scala (Para-comprensión)
  • Haskell (do-notación)
  • F # / OCaml (expresiones de computación / Perform)

Ruby puede emular generadores a través de sus capacidades de continuación integradas.

Common Lisp, aunque no tiene continuaciones nativas, permite crear continuaciones delimitadas usando transformadores CPS como cl-cont . Por lo tanto, los generadores en Common Lisp se pueden escribir de forma muy parecida a los generadores de esquemas.

Por cierto, los generadores basados ​​en la continuación tienen un rasgo que los generadores de Python y C # carecen: el yield puede ser llamado en la extensión dinámica de la llamada a la función del generador. Los generadores Python y C # permiten que el yield se coloque solo dentro del cuerpo de un generador.

Rubí:

Función generadora:

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

En general, el yield es redundante en lenguajes que tienen funciones de primera clase. Por ejemplo, en TIScript puedes hacer generadores de esta manera:

Generador. Tenga en cuenta, devuelve la función interna.

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

Y su uso:

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

for(elem in source) en TIScript es ligeramente diferente de JS. Si source es una función a la que se llama y su valor de retorno se asigna al elem hasta que la función no devuelva void (valor de retorno predeterminado de la función empty).