Yield and Generators
Produce one value at a time, without building a collection. A generator pauses at each yield and resumes when its consumer requests another element.
Your First Generator
import klyn.collections
def squares(limit as Int) as Generator<Int>:
number = 0
while number < limit:
yield number * number
number++
values = squares(5) # Creates the cursor; the body has not run yet.
for value in values:
print(value) # 0, 1, 4, 9, 16
Generator<T> belongs to klyn.collections, but it is not a collection. It has no size, indexer or mutation API. Yielded values follow ordinary static assignment rules for T. Return-type inference also works when the yielded type is unambiguous.
Arguments are evaluated at the call. Value parameters are copied; reference parameters retain their normal references. Local variables survive suspension and keep their lexical scope. Instance and static methods can also contain yield.
Range Is a Generator
for ticket in range(100_000_001):
print(ticket) # 0
break
for value in range(5, 0, -2):
print(value) # 5, 3, 1
range(10) has static type Generator<Int>. The stop bound is exclusive and the step cannot be zero. Large bounds do not allocate a list. Only the iteration state is retained, independently of the number of values.
Neither range nor another generator can be restarted. To repeat the sequence, call the generator function again. A second loop on an exhausted or closed cursor produces no elements.
Manual Iteration
try values = range(3):
if values.next():
print(values.current) # 0
print("Taking a break")
while values.next():
print(values.current) # 1, 2
next() advances and returns a Boolean. The read-only current property reads the resulting value without advancing. Access before the first successful advancement or after completion raises IllegalStateException. close() is idempotent; advancing an exhausted or closed generator returns false.
Early Exit and Resource Safety
def trail() as Generator<String>:
try:
yield "Bridge"
yield "Treasure"
yield "Dragon"
finally:
print("Map closed")
for place in trail():
print(place)
if place == "Treasure":
break # Prints "Map closed"; never visits the dragon.
for closes its generator on normal completion, break, return and exceptions. Resource scopes and active finally blocks are cleaned up deterministically. To pause without closing, use manual iteration within try resource = ....
Closing an unstarted cursor does not execute its body. Exceptions raised by the body are observed while advancing, with the generator's source location preserved.
Language Rules
yield valueis a statement. It is not an assignment expression.- An explicit result annotation is
Generator<T>, not a custom subclass: the compiler creates the cursor implementation. - A bare
returnends a generator. A valued return is invalid in a body containingyield. - Constructors, property accessors and
finallyblocks cannot yield. - Generator functions cannot have
reforoutparameters. - A single cursor must not be resumed or closed while it is already running.
- Generators are synchronous: they do not start threads or asynchronous tasks. The thread scheduling hint is
Thread.relinquish(), notyield.
Try the Treasure Trail
Run klyn samples/Generators.kn. The sample shows a lazy treasure hunt, early closing before the dragon's cave, a large range and a manually resumed countdown.
See also Functions and Exceptions and Resource Safety.