La clave es mantener el estado de la función entre llamadas. Tiene una serie de opciones:
Estado estático (o global). Significa que la secuencia de llamadas a la función no es reentrante, es decir, no puede tener la función de llamada de manera recursiva, ni puede tener más de un llamante ejecutando diferentes secuencias de llamadas.
Inicializar (y posiblemente asignar) el estado en la primera llamada o antes, y pasarla a la función en cada llamada posterior.
Haciendo cosas inteligentes con setjmp/longjmp, la pila o código modificable (hay un artículo en alguna parte sobre funciones de currificación en C que crea un objeto con el código necesario para llamar a la función al curry, una técnica similar podría crear un objeto con el estado de la función y el código necesario para guardarlo y restaurarlo para cada llamada). (Editar encontrado que - http://asg.unige.ch/site/papers/Dami91a.pdf)
Greg cita un artículo interesante, más arriba, que presenta una forma de usar estado estático con una sintaxis similar a la declaración yield
. Me gustó académicamente pero probablemente no lo usaría debido al problema de reentrada, y porque todavía estoy sorprendido de que el infame dispositivo de Duffy compila ;-).
En la práctica, los programas de C grandes sí quieren calcular cosas perezosamente, p. Ej. un servidor de base de datos puede querer satisfacer una consulta SELECT ... LIMIT 10
envolviendo la consulta simple SELECT
dentro de algo que producirá cada fila hasta que se hayan devuelto 10, en lugar de calcular el resultado completo y luego descartar la mayoría de ellos. La técnica más parecida a C para esto es crear explícitamente un objeto para el estado y llamar a una función para cada llamada.Para su ejemplo, es posible ver algo como:
/* Definitions in a library somewhere. */
typedef int M2_STATE;
M2_STATE m2_new() { return 0; }
int m2_empty(M2_STATE s) { return s < INT_MAX; }
int m2_next(M2_STATE s) { int orig_s = s; s = s + 2; return orig_s; }
/* Caller. */
M2_STATE s;
s = m2_new();
while (!m2_empty(s))
{
int num = m2_next(s);
printf("%d\n", num);
}
Esto parece engorroso para los múltiplos de dos, pero se convierte en un patrón útil para generadores más complicados. Puede hacer que el estado sea más complicado sin tener que cargar todo el código que usa su generador con los detalles. Una práctica aún mejor es devolver un puntero opaco en la función new
y (a menos que GC esté disponible) proporcionar una función para limpiar el generador.
La gran ventaja de asignar el estado para cada nueva secuencia de llamadas es cosas como generadores recursivos. Por ejemplo, un generador que devuelve todos los archivos bajo un directorio, llamándose a sí mismo en cada subdirectorio.
char *walk_next(WALK_STATE *s)
{
if (s->subgenerator)
{
if (walk_is_empty(s->subgenerator))
{
walk_finish(s->subgenerator);
s->subgenerator = NULL;
}
else
return walk_next(s->subgenerator);
}
char *name = readdir(s->dir);
if (is_file(name))
return name;
else if (is_dir(name))
{
char subpath[MAX_PATH];
strcpy(subpath, s->path);
strcat(subpath, name);
s->subgenerator = walk_new(subpath);
return walk_next(s->subgenerator);
}
closedir(s->dir);
s->empty = 1;
return NULL;
}
(Vas a tener que disculpar mi mal uso de readdir, et al., Y mi pretensión de que C tiene soporte de serie a prueba de idiotas.)
Ok, ahora tengo un problema con mi propio código. Definir 'multiples_of_2' como' {0, * next_multiple} 'parece extraño (¿por qué desreferencia?), Así que probé' {0, next_multiple} 'y' {0, y next_multiple} '. Todos funcionan ... Siento que me estoy perdiendo algo. – jdb
@jdb: debe actualizar la respuesta. 'nest_multiple' y' & nest_multiple' es lo mismo, ya que el operador de dirección siempre está implícitamente allí para un puntero de función. – u0b34a0f6ae
Gracias, no lo sabía. – jdb