Async Programming

Keikaku provides built-in primitives for non-blocking operations, including async protocols, promises, and deferred execution - all integrated seamlessly with the sequence/generator system.

Async Primitives

  • async protocol - Define asynchronous functions
  • await - Wait for promise resolution
  • resolve(value) - Create resolved promise
  • defer(ms, fn, ...args) - Delayed execution
  • sleep(ms) - Pause execution

Async Protocols

An async protocol is a function that can perform non-blocking operations. It returns a generator that can be advanced with proceed() or awaited.

KEIKAKU Protocol
1# Define an async protocol
2async protocol fetch_user(id):
3 declare("Fetching user", id, "...")
4 sleep(200) # Simulate network delay
5 yield {
6 "id": id,
7 "name": "User " + text(id),
8 "status": "active"
9 }
10
11# Execute the async protocol
12gen := fetch_user(42)
13result := proceed(gen)
14declare("Got user:", result)
15
16# Output:
17# Fetching user 42 ...
18# Got user: {...}

Chaining Async Operations

KEIKAKU Protocol
1async protocol fetch_user(id):
2 sleep(100)
3 yield {"id": id, "name": "User " + text(id)}
4
5async protocol fetch_posts(user_id):
6 sleep(100)
7 yield [
8 {"title": "Post 1 by " + text(user_id)},
9 {"title": "Post 2 by " + text(user_id)}
10 ]
11
12async protocol get_user_with_posts(id):
13 # Chain async calls
14 user_gen := fetch_user(id)
15 user := proceed(user_gen)
16
17 posts_gen := fetch_posts(user["id"])
18 posts := proceed(posts_gen)
19
20 user["posts"] := posts
21 yield user
22
23# Execute the chain
24gen := get_user_with_posts(1)
25data := proceed(gen)
26declare("User with posts:", data)

Promises

Promises represent values that will be available in the future. Use resolve() to create a promise and await to get its value.

KEIKAKU Protocol
1# Create a resolved promise
2promise := resolve("completed")
3
4# Await the promise
5value := await promise
6declare("Promise resolved with:", value)
7# Output: Promise resolved with: completed
8
9# Promises from async protocols
10async protocol compute_heavy():
11 sleep(500) # Expensive operation
12 yield 42 * 42
13
14# Start the operation (non-blocking)
15computation := compute_heavy()
16
17# Do other work while computation runs
18declare("Doing other work...")
19sleep(100)
20declare("Still working...")
21
22# Now get the result
23result := proceed(computation)
24declare("Computation result:", result)
25# Output: 1764

Promise Patterns

KEIKAKU Protocol
1# Immediate resolution
2immediate := resolve(100)
3declare(await immediate) # 100
4
5# Creating promise-like behavior
6async protocol delayed_value(val, ms):
7 sleep(ms)
8 yield val
9
10# Multiple concurrent operations
11p1 := delayed_value("first", 300)
12p2 := delayed_value("second", 200)
13p3 := delayed_value("third", 100)
14
15# Results come back in completion order
16declare(proceed(p3)) # third (100ms)
17declare(proceed(p2)) # second (200ms)
18declare(proceed(p1)) # first (300ms)

Sleep and Timing

The sleep(ms) function pauses execution for the specified milliseconds.

KEIKAKU Protocol
1declare("Starting...")
2
3# Sleep for 1 second
4sleep(1000)
5declare("1 second passed")
6
7# Sleep for half a second
8sleep(500)
9declare("1.5 seconds total")
10
11# Practical use: Rate limiting
12protocol rate_limited_fetch(urls):
13 cycle through urls as url:
14 declare("Fetching:", url)
15 sleep(100) # 100ms between requests
16 yield "Data from " + url
17
18urls := ["api/1", "api/2", "api/3"]
19cycle through rate_limited_fetch(urls) as data:
20 declare("Received:", data)

Deferred Execution

defer(ms, protocol, ...args) schedules a protocol to run after a delay without blocking.

KEIKAKU Protocol
1# Define a callback
2protocol notify(message):
3 declare("📢 Notification:", message)
4
5# Schedule for later
6defer(2000, notify, "Task completed!")
7defer(1000, notify, "Still processing...")
8defer(500, notify, "Started!")
9
10declare("All tasks scheduled, continuing execution...")
11
12# Output order:
13# All tasks scheduled, continuing execution...
14# (after 500ms) 📢 Notification: Started!
15# (after 1000ms) 📢 Notification: Still processing...
16# (after 2000ms) 📢 Notification: Task completed!

Debouncing Pattern

KEIKAKU Protocol
1# Simple debounce implementation
2protocol handle_input(text):
3 declare("Processing input:", text)
4
5# Simulate rapid user input
6defer(0, handle_input, "H")
7defer(50, handle_input, "He")
8defer(100, handle_input, "Hel")
9defer(150, handle_input, "Hell")
10defer(200, handle_input, "Hello") # Only process this
11
12# In practice, you'd cancel previous defers
13# This is a simplified example

Practical Examples

API Client

KEIKAKU Protocol
1# Simulated API client
2async protocol api_get(endpoint):
3 declare("GET", endpoint)
4 sleep(200) # Network latency
5
6 foresee endpoint == "/users":
7 yield [{"id": 1}, {"id": 2}]
8 alternate endpoint == "/posts":
9 yield [{"title": "Hello"}, {"title": "World"}]
10 otherwise:
11 yield {"error": "Not found"}
12
13# Usage
14users_gen := api_get("/users")
15posts_gen := api_get("/posts")
16
17users := proceed(users_gen)
18posts := proceed(posts_gen)
19
20declare("Users:", users)
21declare("Posts:", posts)

Polling Pattern

KEIKAKU Protocol
1# Poll for status updates
2async protocol poll_status(max_attempts):
3 attempts := 0
4 cycle while attempts < max_attempts:
5 attempts = attempts + 1
6 declare("Checking status (attempt", attempts, ")...")
7
8 sleep(500)
9
10 # Simulate random completion
11 foresee random(1, 10) > 7:
12 yield "completed"
13 otherwise:
14 yield "pending"
15
16gen := poll_status(5)
17cycle:
18 status := proceed(gen)
19 declare("Status:", status)
20 foresee status == "completed":
21 declare("✓ Task finished!")
22 terminate

Timeout Pattern

KEIKAKU Protocol
1# Execute with timeout
2async protocol with_timeout(operation, timeout_ms):
3 start := timestamp()
4 gen := operation()
5
6 result := proceed(gen)
7 elapsed := (timestamp() - start) * 1000
8
9 foresee elapsed > timeout_ms:
10 yield {"error": "Timeout exceeded"}
11 otherwise:
12 yield result
13
14async protocol slow_operation():
15 sleep(2000)
16 yield "Success"
17
18# This will timeout
19gen := with_timeout(slow_operation, 1000)
20result := proceed(gen)
21declare(result) # {"error": "Timeout exceeded"}

Best Practices

  • 1. Keep async protocols focused - One async operation per protocol
  • 2. Handle timeout scenarios - Network operations can hang
  • 3. Use defer for fire-and-forget - When you don't need the result
  • 4. Chain with intermediate proceeds - Not all at once at the end
  • 5. Add logging in async code - Debugging async is harder