a wrapper around gen blocks. it has several different forms and purposes
the generator macro, in its simplist form, wraps a block and allows the use of the yield keyword within.
it transforms wrapped code into a coroutine that can stop at specific breakpoints and transfer data out, denoted by the yield keyword.
after the yield keyword, you then provide a value for the generator to yield. each time the generator yields,
it pauses, and returns an Option of the value, where None represents the completion of the generator.
when created, a generator only runs when you call .next(). this is because generators are represented by iterators.