[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: A Problem with Python's 'yield'



On Tue, 2003-05-27 at 12:53, Jeremy Hylton wrote:
> On Tue, 2003-05-27 at 11:15, Eric Kidd wrote:
> > I'm going to pick on Python here, but only because the example code will
> > be short and sweet. :-) I believe several other implementations of
> > generators have the same problem.
> 
> I expect you'll find this limitation in every language that is
> imlemented using a C stack and recursion for function calls.  Python's
> generator are sometimes called "simple generators" because they are
> simple to implement.  There is only a single function call frame that
> needs to be kept around after a generator yields a value.

Actually, it's possible to implement a recursive yield_all in portable
ANSI C.  It's just ugly. :-)

I've attached some C code for the non-recursive case.  It (ab)uses
Duff's Device to temporarily suspend a C function.  Local variables of
the C function are lifted into a struct (as though you were implementing
a closure).

To generalize to the recursive case, you need to add a front-end
function which builds a stack (or linked list) of structs and keeps
track of which iterator is currently active.  If you do a bit of extra
work, you can ensure this front-end function only gets called for
recursive iterators, and let non-recursive ones run at full speed.

I think this is just about as evil as you can get within the constraints
of ANSI C.

> The Jython developers tell us they'd have trouble, too.

I haven't spent much time thinking about recursive iterators on the
JVM.  I suspect that the JVM doesn't like Duff's Device very much.

Cheers,
Eric


#include <stdio.h>

/*=========================================================================
**  Generator Co-Routines in C
**=========================================================================
**  We want to compile the following Sather-inspired generator into
**  plausible C code:
**
**  def twoway (n: int): int
**    for (var i: int = 0; i < n; i=i+1)
**      yield i
**    for (var j: int = (n-1); j >= 0; j=j+1)
**      yield j
**
**  for (var i in twoway(10))
**    printf("next = %s\n", i)
**
**  To do this, we abuse some interesting properties of C's switch
**  statement.  Much credit is due to Duff's device; all the blame is
**  due to me.
**
**  Eric Kidd
**  eric.kidd@pobox.com
*/

typedef struct {
    // Saved iteration state.
    int state;

    // Lift all the local bindings into an environment object.
    int n;
    int i;
    int j;
} twoway_env;
  
void twoway_init (twoway_env *env, int n) {
    env->state = 1;
    env->n = n;
};

#define GENERATOR_HEAD(env) switch ((env)->state) { case 1:
#define GENERATOR_QUIT(env) (env)->state = 0; return 0
#define GENERATOR_TAIL(env) case 0: GENERATOR_QUIT(env); }
#define GENERATOR_YIELD(env,n) (env)->state = (n); return 1; case n:

// Returns true iff it was able to find an element.
int twoway_next (twoway_env *env, int *yielded_value) {
    GENERATOR_HEAD(env);

    for (env->i = 0; env->i < env->n; env->i++) {
        *yielded_value = env->i;
        GENERATOR_YIELD(env, 2);
    }
    for (env->j = (env->n - 1); env->j >= 0; env->j--) {
        *yielded_value = env->j;
        GENERATOR_YIELD(env, 3);
    }
    GENERATOR_TAIL(env);
}

int main (int argc, char **argv) {
    twoway_env env;
    int i;

    twoway_init(&env, 10);
    while (twoway_next(&env, &i)) {
        printf("next = %d\n", i);
    }

    return 0;
}