NAME

Acme::Parataxis::Generator - Stackful, Lazily-Pulled Coroutine Iterators

SYNOPSIS

use v5.40;
use Acme::Parataxis::Generator;

my $fib = Acme::Parataxis::Generator->new( sub ($y) {
    my ( $a, $b ) = ( 0, 1 );
    while ( $a < 100 ) {
        $y->($a);
        ( $a, $b ) = ( $b, $a + $b );
    }
} );

my $total = 0;
while ( defined( my $n = $fib->next ) ) {
    $total += $n;
}
say "Sum of Fibonacci numbers < 100: $total";    # 232

DESCRIPTION

A generator executes its producer body on an isolated, private coroutine fiber and produces values on demand. The body is plain synchronous Perl: it can loop, return, call functions, and recurse deeply. Calling $y->($value) from any depth suspends the producer immediately, preserving lexical variables, loop states, and call frames intact. The next invocation of ->next resumes execution right where it paused.

Key properties:

  • Lazy evaluation: Nothing inside the body executes until the first call to next(). An infinite sequence costs nothing until pulled, and consumes only as much memory and CPU as needed for the values requested.

  • Full coroutine ergonomics: Unlike shallow iterators, yielding is not restricted to the top-level block; helper subs can accept $y and yield on behalf of the generator.

  • Safe lifecycle teardown: Abandoning an unexhausted generator cleanly drains the fiber: the yield site raises an internal drain sentinel, unwinding lexical scopes and executing object destructors without leaking fibers.

  • Context independence: Generators do not enter the scheduler run queue. They can be instantiated and pulled from top-level mainline scripts, within async blocks, inside spawned fibers, or inside nurseries.

CONSTRUCTOR

new( sub ($y) { ... } )

my $gen = Acme::Parataxis::Generator->new( sub ($y) {
    $y->($_) for 1 .. 3;
} );

Constructs and returns a new generator.

The constructor takes exactly one argument: a code reference representing the producer body. That code reference receives a single parameter: the yield closure (conventionally named $y).

Calling $y->($value) suspends the body and delivers $value to the caller's next(). $y requires exactly one argument; calling $y->() or passing multiple arguments croaks.

METHODS

next( )

my $val = $gen->next;

Resumes the generator body and returns the next yielded value.

  • Once the body finishes (either by falling off the end or returning), next() returns undef and continues returning undef on subsequent calls.

  • If the body explicitly yields undef (e.g. $y->(undef)), next() returns undef. Use "is_done( )" to differentiate a yielded undef from sequence exhaustion.

  • If the body throws an exception, that exception is rethrown directly on the caller's stack at the next() call site, and the generator is marked permanently finished. Exception objects cross the resumption boundary intact.

is_done( )

while ( !$gen->is_done ) {
    my $v = $gen->next;
    process($v) if defined $v;
}

Returns true if the generator has finished execution (whether it returned normally or threw an exception), and false if it is still suspended awaiting the next pull.

fiber( )

Returns the underlying private Acme::Parataxis fiber object. Intended for debugging and ID inspection.

Recipes

Safely consuming sequences that contain undef

The idiom while ( defined(my $v = $gen->next) ) halts prematurely if the sequence legitimately yields undef. Test is_done() instead:

sub drain ($gen) {
    my @items;
    while (1) {
        my $val = $gen->next;
        last if $gen->is_done;
        push @items, $val;
    }
    return @items;
}

my $gen = Acme::Parataxis::Generator->new( sub ($y) {
    $y->('alpha');
    $y->(undef);
    $y->('omega');
} );

my @values = drain($gen);    # [ 'alpha', undef, 'omega' ]

Composing lazy generator pipelines

my $cubes = Acme::Parataxis::Generator->new( sub ($y) {
    my $ints = Acme::Parataxis::Generator->new( sub ($z) {
        my $i = 1;
        while (1) { $z->($i++) }
    } );

    while ( defined( my $n = $ints->next ) ) {
        $y->( $n ** 3 );
    }
} );

say $cubes->next for 1 .. 4;    # 1, 8, 27, 64

Exception propagation preserving error objects

use Acme::Parataxis::Error;

my $gen = Acme::Parataxis::Generator->new( sub ($y) {
    $y->(1);
    Acme::Parataxis::Error::Timeout->throw( seconds => 5 );
} );

say $gen->next;    # 1
eval { $gen->next };
if ( my $err = $@ ) {
    say "Caught typed error: " . $err->kind;    # 'timeout'
}

Behavior Notes

  • Body return values are discarded

    A generator's output must be produced via $y->(...). If a body terminates via return $val, that final return value is ignored, and the next next() returns undef. Always yield the final value before returning.

  • Do not park inside a generator body

    Generators run outside the cooperative scheduler queue. Calling cooperative parking primitives (await_sleep, await_read, or channel get) inside a generator injects scheduler control tokens (such as 'WAITING') into the sequence as yielded values. Keep the generator body synchronous; perform async I/O on the consumer side.

  • Abandoned generators drain on destruction

    If a generator goes out of scope while suspended at a yield point, its DESTROY handler resumes the private fiber with an internal drain sentinel. This causes the yield call to throw internally, allowing enclosing scopes, destructors, and finally blocks in the generator to execute cleanups.

  • Reserved fiber 0 on Windows

    On Windows, longjmp-based unwinding across the very first fiber in a process (ID 0) can bypass anchored exception traps. To guarantee stability, the first generator allocated at the mainline permanently reserves fiber ID 0, ensuring generators always execute on fiber ID 1 or higher. Inside a scheduled run() block, the root fiber already holds ID 0, so no extra reservation is created.

SEE ALSO

Acme::Parataxis, Acme::Parataxis::Channel, Acme::Parataxis::Stream

AUTHOR

Sanko Robinson https://github.com/sanko

LICENSE

Copyright (C) Sanko Robinson.

This library is free software; you can redistribute it and/or modify it under the terms found in the Artistic License 2.