Generators & Sequences

Sequences are Keikaku's most powerful feature - lazy iterators that produce values on-demand, support bidirectional communication, and enable memory-efficient stream processing.

Why Use Sequences?

  • Memory Efficient - Process millions of items without loading all in memory
  • Lazy Evaluation - Values computed only when needed
  • Infinite Streams - Generate unbounded sequences
  • Two-Way Communication - Send data INTO running generators

Creating Sequences

Define a sequence with the sequence keyword. Use yield to produce values.

KEIKAKU Protocol
1# Simple counter sequence
2sequence count_up(start, end):
3 i := start
4 cycle while i <= end:
5 yield i
6 i = i + 1
7
8# Iterate with cycle through
9cycle through count_up(1, 5) as num:
10 declare("Number:", num)
11
12# Output:
13# Number: 1
14# Number: 2
15# Number: 3
16# Number: 4
17# Number: 5

Manual Iteration with proceed()

For fine-grained control, use proceed() to advance the generator step by step.

KEIKAKU Protocol
1sequence fibonacci():
2 a := 0
3 b := 1
4 cycle while true:
5 yield a
6 temp := a + b
7 a = b
8 b = temp
9
10# Get first 10 Fibonacci numbers manually
11fib := fibonacci()
12cycle from 0 to 10 as _:
13 declare(proceed(fib))
14
15# Output: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

Delegation (yield from)

The delegate keyword flattens nested generators, yielding all values from an inner sequence.

KEIKAKU Protocol
1sequence header():
2 yield "=== START ==="
3 yield "Initializing..."
4
5sequence body():
6 yield "Processing item 1"
7 yield "Processing item 2"
8 yield "Processing item 3"
9
10sequence footer():
11 yield "Cleanup complete"
12 yield "=== END ==="
13
14# Main sequence delegates to sub-sequences
15sequence full_report():
16 delegate header()
17 delegate body()
18 delegate footer()
19
20cycle through full_report() as line:
21 declare(line)
22
23# Output:
24# === START ===
25# Initializing...
26# Processing item 1
27# Processing item 2
28# Processing item 3
29# Cleanup complete
30# === END ===

Recursive Delegation

KEIKAKU Protocol
1# Tree traversal with delegation
2sequence flatten(items):
3 cycle through items as item:
4 foresee classify(item) == "list":
5 delegate flatten(item) # Recurse into nested list
6 otherwise:
7 yield item
8
9nested := [1, [2, 3, [4, 5]], 6, [7]]
10cycle through flatten(nested) as val:
11 declare(val)
12
13# Output: 1, 2, 3, 4, 5, 6, 7

Bidirectional Communication

Keikaku sequences support sending values INTO a running generator using transmit() and receive().

Echo Pattern

KEIKAKU Protocol
1sequence echo():
2 cycle while true:
3 received := receive()
4 foresee received:
5 yield "Echo: " + text(received)
6 otherwise:
7 yield "Waiting..."
8
9gen := echo()
10declare(proceed(gen)) # Waiting...
11declare(transmit(gen, "hello")) # Echo: "hello"
12declare(transmit(gen, 42)) # Echo: 42
13declare(transmit(gen, "world")) # Echo: "world"

Running Accumulator

KEIKAKU Protocol
1sequence accumulator():
2 total := 0
3 cycle while true:
4 received := receive()
5 foresee received:
6 total = total + received
7 yield total
8
9acc := accumulator()
10proceed(acc) # Initialize (yields 0)
11declare(transmit(acc, 10)) # 10
12declare(transmit(acc, 5)) # 15
13declare(transmit(acc, 25)) # 40
14declare(transmit(acc, 100)) # 140

Running Average Calculator

KEIKAKU Protocol
1sequence running_avg():
2 total := 0.0
3 count := 0
4 cycle while true:
5 received := receive()
6 foresee received:
7 total = total + received
8 count = count + 1
9 foresee count > 0:
10 yield total / count
11 otherwise:
12 yield 0.0
13
14avg := running_avg()
15proceed(avg)
16declare(transmit(avg, 10)) # 10.0
17declare(transmit(avg, 20)) # 15.0 (30/2)
18declare(transmit(avg, 30)) # 20.0 (60/3)

Generator Expressions

For simple transformations, use inline generator expressions - they're concise and lazy.

KEIKAKU Protocol
1items := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2
3# Lazy squares (computed on-demand)
4squares := (x * x for x through items)
5
6# Filtered values
7evens := (x for x through items where x % 2 == 0)
8
9# Combined: squares of even numbers
10even_squares := (x * x for x through items where x % 2 == 0)
11
12cycle through even_squares as val:
13 declare(val)
14# Output: 4, 16, 36, 64, 100

Exception Injection with disrupt()

The disrupt() function throws an exception INTO a generator, which can be caught with attempt/recover.

KEIKAKU Protocol
1sequence worker():
2 cycle while true:
3 attempt:
4 yield "Working..."
5 recover as err:
6 declare("Worker caught:", err)
7 yield "Recovered"
8
9gen := worker()
10declare(proceed(gen)) # Working...
11declare(disrupt(gen, "Stop")) # Worker caught: Stop
12 # Recovered
13declare(proceed(gen)) # Working...

Practical Examples

Paginated Data Fetcher

KEIKAKU Protocol
1sequence fetch_pages(total_pages):
2 page := 1
3 cycle while page <= total_pages:
4 # Simulate fetching page data
5 sleep(100)
6 yield "Page " + text(page) + " data"
7 page = page + 1
8
9# Process pages lazily - only fetch when needed
10cycle through fetch_pages(5) as data:
11 declare("Processing:", data)
12 # Can stop early if needed
13 foresee contains(data, "3"):
14 declare("Found target, stopping")
15 terminate

State Machine

KEIKAKU Protocol
1sequence traffic_light():
2 states := ["GREEN", "YELLOW", "RED"]
3 index := 0
4 cycle while true:
5 yield states[index]
6 index = (index + 1) % 3
7
8light := traffic_light()
9cycle from 0 to 7 as _:
10 declare("Light is:", proceed(light))
11
12# Output cycles: GREEN, YELLOW, RED, GREEN, YELLOW, RED, GREEN

Best Practices

  • 1. Prefer delegation over nested loops - Keep generators flat and composable
  • 2. Use receive() for stateful generators - Enable external control of internal state
  • 3. Add cleanup logic before termination - Handle resources properly
  • 4. Prefer generator expressions for simple transforms - More readable than full sequence definitions