c++ - varios - duolingo
Generadores de pitón en varios idiomas (9)
¿Cómo emulas los generadores de estilo Python en tu idioma favorito? Encontré this en el esquema. Debe ser interesante ver otras implementaciones, especialmente en aquellos idiomas que no tienen continuaciones de primera clase.
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 el 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 sintaxis 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.
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 están las primitivas rutinas C setjmp, longjmp para lograr resultados similares.
(Lua coroutines se implementan con el método anterior)
C ++, usando codeproject.com/Articles/29524/Generators-in-C
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
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.
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 pitónicos 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)]
En general, el yield
es redundante en idiomas 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).
Rubí:
Función generadora:
def simple_range(n)
Enumerator.new do |y|
(0..n).each { |v| y.yield(v) }
end
end
Monads 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 sintaxis 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.
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 se pueden implementar 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 del 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 Common Lisp SERIES :
(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.