Generators

Generators return a series of values, one at a time.

Generator Functions

Generator functions are defined almost exactly like functions, except that their type is gen instead of fn.

gen single_evens(): (int) {
    var x: 2

    while (x <= 8) {
        yield x

        x += 2
    }
}

gen single_evens_reversed(): (int) {
    var x: 8

    while (x >= 2) {
        yield x

        x -= 2
    }
}

gen all_evens(): (int) {
    var x: 2

    yield x

    x += 2
}

Generator Expressions

A generator expression defines a generator in a single line.

fn main() {
    val single_evens: (n for n in [1..9] if n % 2 == 0)
    val single_evens_backwards: (n for n in [9..1, -1] if n % 2 == 0)
    val all_evens: (n for n in [1..] if n % 2 == 0)
}

Note also the range expressions [1..9], [9..1, -1], and [1..]. See ranges for more information.

Using Generators

Generators can be used in for-in blocks, or inside a with block.

fn main() {
    val single_evens: (n for n in [1..9] if n % 2 == 0)
    val single_evens_backwards: (n for n in [9..1, -1] if n % 2 == 0)
    val all_evens: (n for n in [1..] if n % 2 == 0)

    with (single_evens.next() as single_even) {
        echo("First single even: ${single_even}")
    }

    for (single_even in single_evens) {
        echo("Next single even: ${single_even}")
    }

    echo("No more single evens!")
}

This restriction is necessary as generators may stop yielding values at any time, and outside a with or for-in block, the variable would potentially be left uninitialized.

Performance Considerations

Generators are much slower than static sequences like arrays; laziness is the main reason to use them. The canonical example is a Fibonacci sequence: rather than computing each Fibonacci number and storing the result in a large array, you can generate each successive value as you need it.


Generators vs. Coroutines

I think getting rid of gen in favor of coro is a lot better. You pretty much have to implement coroutines to have generators anyway, so it's fine. FWIW, the implementation is:

gen fib() (uint) {
    var number3: 0
    var number2: 1
    var number1: 0

    number1 = number2 + number3

    yield(number1)

    number3 = number2
    number2 = number1
}

fn main() {
    var fib_gen: fib()

    for (fibn in fib_gen) {
        echo("Fib: {{fibn}}");
    }
}
typedef void(fib_gen_func_type)(fib_gen_context *context, uint *out, Error *e);

typedef struct {
    bool _finished;
    uint number3
    uint number2
    uint number1
    fib_gen_func_type next;
} fib_gen_context;

void fib_gen_func(fib_gen_context *context, uint *out, Error *e) {
    context->number1 = context->number2 + context->number3;
    *out = context->current_number;
    context->number3 = context->number2
    context->number2 = context->number1
}

void init_fib_gen_context(fib_gen_context *context) {
    context->_finished = false;
    context->number3 = 0;
    context->number2 = 1;
    context->number1 = 0;
    context->next = fib_gen_func;
}

int main(void) {
    fib_gen_context fib_gen;
    init_fib_gen_context(&fib_gen);

    while (!fib_gen.finished) {
        Error e;
        uint next_fib_number;

        fib_gen.next(&fib_gen, &next_fib_number, &e);

        if (e.occurred) {
            handle_error(&e);
        }

        printf("Fib: %d\n", next_fib_number);
    }
}