Keikaku provides structured error handling through attempt/recover blocks and the ability to inject exceptions into running generators.
attempt - Execute code that might failrecover as err - Catch and handle errorsdisrupt(gen, error) - Throw exception into generatorWrap risky code in attempt blocks and handle errors in recover blocks.
1# Basic error handling2attempt:3 declare("Attempting risky operation...")4 result := 10 / 0 # Division by zero!5 declare("This won't print")6recover as error:7 declare("Caught error:", error)89declare("Program continues normally")1011# Output:12# Attempting risky operation...13# Caught error: Division by zero14# Program continues normally1# Return default value on error2protocol safe_divide(a, b):3 attempt:4 foresee b == 0:5 yield 0 # Safe default6 otherwise:7 yield a / b8 recover as e:9 declare("Division error:", e)10 yield 01112declare(safe_divide(10, 2)) # 513declare(safe_divide(10, 0)) # 0 (safe default)1415# Retry pattern16protocol fetch_with_retry(url, max_retries):17 attempts := 018 cycle while attempts < max_retries:19 attempts = attempts + 120 attempt:21 declare("Attempt", attempts, "to fetch", url)22 # Simulate random failure23 foresee random(1, 10) > 5:24 yield "Success!"25 otherwise:26 x := unknown_var # Force error27 recover as e:28 declare("Failed, retrying...")29 sleep(100)30 31 yield "All retries exhausted"1# Inner errors can propagate to outer handlers2attempt:3 declare("Outer attempt")4 5 attempt:6 declare("Inner attempt")7 x := undefined_variable8 declare("Won't reach here")9 recover as inner_err:10 declare("Inner caught:", inner_err)11 # Re-raise by causing another error12 foresee text(inner_err) == "important":13 y := another_undefined # Propagate14 15 declare("Outer continues")1617recover as outer_err:18 declare("Outer caught:", outer_err)The disrupt(generator, error) function throws an exception INTO a running generator, which can be caught with attempt/recover inside the generator.
1# Generator that handles injected exceptions2sequence worker():3 task_count := 04 cycle while true:5 attempt:6 task_count = task_count + 17 yield "Completed task " + text(task_count)8 recover as err:9 declare("Worker interrupted:", err)10 yield "Recovered from " + text(err)1112gen := worker()13declare(proceed(gen)) # Completed task 114declare(proceed(gen)) # Completed task 215declare(disrupt(gen, "Timeout")) # Worker interrupted: Timeout16 # Recovered from Timeout17declare(proceed(gen)) # Completed task 31# Generator with cleanup on exception2sequence file_processor(files):3 declare("Opening resources...")4 5 cycle through files as file:6 attempt:7 declare("Processing:", file)8 yield "Processed " + file9 recover as err:10 declare("Error processing", file, "-", err)11 # Cleanup before dying12 declare("Closing resources...")13 yield "Shutdown complete"1415gen := file_processor(["a.txt", "b.txt", "c.txt"])1617declare(proceed(gen)) # Processing: a.txt18declare(proceed(gen)) # Processing: b.txt1920# Inject shutdown signal21declare(disrupt(gen, "SIGTERM"))22# Output:23# Error processing b.txt - SIGTERM24# Closing resources...25# Shutdown complete1protocol validate_user(user):2 errors := []3 4 foresee measure(user["name"]) < 2:5 push(errors, "Name too short")6 7 foresee user["age"] < 0:8 push(errors, "Age cannot be negative")9 10 foresee measure(errors) > 0:11 yield {"valid": false, "errors": errors}12 otherwise:13 yield {"valid": true, "data": user}1415result := validate_user({"name": "A", "age": -5})16foresee result["valid"] == false:17 declare("Validation failed:", result["errors"])18# Output: Validation failed: ["Name too short", "Age cannot be negative"]1# Collect all errors instead of failing on first2sequence process_batch(items):3 errors := []4 successes := 05 6 cycle through items as item:7 attempt:8 foresee item < 0:9 x := invalid # Force error for negative10 successes = successes + 111 yield "Processed: " + text(item)12 recover as e:13 push(errors, {"item": item, "error": text(e)})14 yield "Skipped: " + text(item)15 16 yield {17 "total": measure(items),18 "successes": successes,19 "errors": errors20 }2122items := [1, 2, -3, 4, -5, 6]23cycle through process_batch(items) as result:24 declare(result)2526# Final result shows: 4 successes, 2 errors1# Stop calling a failing service2sequence circuit_breaker(threshold):3 failures := 04 is_open := false5 6 cycle while true:7 foresee is_open:8 yield {"status": "circuit_open", "message": "Service unavailable"}9 otherwise:10 received := receive()11 attempt:12 # Simulate calling service13 foresee random(1, 10) > 7:14 failures = 0 # Reset on success15 yield {"status": "success", "data": received}16 otherwise:17 x := fail # Simulate failure18 recover as e:19 failures = failures + 120 foresee failures >= threshold:21 is_open = true22 declare("Circuit opened after", threshold, "failures")23 yield {"status": "error", "failures": failures}2425breaker := circuit_breaker(3)26cycle from 0 to 10 as i:27 declare(transmit(breaker, "request " + text(i)))