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