Walker Responses
Walker Response Patterns
Section titled “Walker Response Patterns”This reference explains how walker responses work and the common patterns for handling them.
Related:
- Graph Operations - Node creation, traversal, and deletion
- Part III: OSP - Walker and node fundamentals
- Build a Todo App - Full tutorial using these patterns
The .reports Array
Section titled “The .reports Array”Every time a walker executes a report statement, the value is appended to a .reports array. When you spawn a walker, you receive this array in the response.
walker:priv MyWalker { can do_work with Root entry { report "first"; # reports[0] report "second"; # reports[1] }}
with entry { # Spawning the walker response = root spawn MyWalker(); print(response.reports); # ["first", "second"]}Common Patterns
Section titled “Common Patterns”Pattern 1: Single Report (Recommended)
Section titled “Pattern 1: Single Report (Recommended)”The cleanest pattern accumulates data internally and reports once at the end:
node Item { has data: str;}
walker:priv ListItems { has items: list = [];
can collect with Root entry { visit [-->]; }
can gather with Item entry { self.items.append(here.data); }
can finish with Root exit { report self.items; # Single report with all data }}
with entry { # Usage result = root spawn ListItems(); items = result.reports[0]; # The complete list}When to use: Most read operations, listing data, aggregations.
Pattern 2: Report Per Node
Section titled “Pattern 2: Report Per Node”Reports each item as it’s found during traversal:
node Item { has name: str;}
walker:priv FindMatches { has search_term: str;
can search with Root entry { visit [-->]; }
can check with Item entry { if self.search_term in here.name { report here; # One report per match } }}
with entry { # Usage result = root spawn FindMatches(search_term="test"); matches = result.reports; # Array of all matching nodes}When to use: Search operations, filtering, finding specific nodes.
Pattern 3: Operation + Result
Section titled “Pattern 3: Operation + Result”Performs an operation and reports a summary:
node Item { has name: str;}
walker:priv CreateItem { has name: str;
can create with Root entry { new_item = here ++> Item(name=self.name); report new_item[0]; # Report the created item }}
with entry { # Usage result = root spawn CreateItem(name="New Item"); created = result.reports[0]; # The new item}When to use: Create, update, delete operations.
Pattern 4: Nested Walker Spawning
Section titled “Pattern 4: Nested Walker Spawning”When one walker spawns another, their reports combine:
walker:priv InnerWalker { can work with Root entry { report "inner data"; }}
walker:priv OuterWalker { can work with Root entry { # Spawn inner walker - its reports go to OUR reports array inner_result = root spawn InnerWalker();
# inner_result.reports[0] = "inner data" # But this is a NEW response object, not added to our reports
# Our own report report {"outer": "data", "inner": inner_result.reports[0]}; }}
with entry { # Usage result = root spawn OuterWalker(); # result.reports[0] = {"outer": "data", "inner": "inner data"}}Important: Nested walker spawns return their own response object. Their reports don’t automatically merge with the parent walker’s reports.
Pattern 5: Multiple Reports (Complex Operations)
Section titled “Pattern 5: Multiple Reports (Complex Operations)”Some operations naturally produce multiple reports:
def do_processing(input: str) -> list { return [input, input + "_processed"];}
walker:priv ProcessAndSummarize { has input: str;
can process with Root entry { # First report: raw results results = do_processing(self.input); report results;
# Second report: summary report { "count": len(results), "status": "complete" }; }}
with entry { # Usage result = root spawn ProcessAndSummarize(input="data"); raw_results = result.reports[0]; # First report summary = result.reports[1]; # Second report}When to use: Operations that produce both detailed results and summaries.
Safe Access Patterns
Section titled “Safe Access Patterns”Always handle the possibility of empty reports:
walker:priv MyWalker { can work with Root entry { report "data"; }}
def process(item: any) { print(item);}
with entry { # Safe single report access result = root spawn MyWalker(); data = result.reports[0] if result.reports else None;
# Safe with default value data = result.reports[0] if result.reports else [];
# Check length for multiple reports if result.reports and len(result.reports) > 1 { first = result.reports[0]; second = result.reports[1]; }
# Iterate all reports for item in (result.reports if result.reports else []) { process(item); }}Response Object Structure
Section titled “Response Object Structure”The full response object from root spawn Walker():
walker:priv MyWalker { can work with Root entry { report "result"; }}
with entry { response = root spawn MyWalker();
# Available properties print(response.reports); # Array of all reported values}Best Practices
Section titled “Best Practices”- Prefer single reports - Accumulate data and report once at the end
- Use
with Root exit- Best place for final reports after traversal - Document report structure - Comment what each report index contains
- Always check
.reports- It may be empty or undefined - Keep reports serializable - Stick to dicts, lists, strings, numbers, bools
Anti-Patterns
Section titled “Anti-Patterns”Don’t: Report in a loop without accumulation
Section titled “Don’t: Report in a loop without accumulation”node Item { has data: str;}
# Bad: Creates many small reportswalker:priv BadPattern { can process with Item entry { report here.data; # N reports for N items }}
# Good: Accumulate and report oncewalker:priv GoodPattern { has items: list = [];
can start with Root entry { visit [-->]; }
can process with Item entry { self.items.append(here.data); }
can finish with Root exit { report self.items; # One report with all items }}Don’t: Assume report order without documentation
Section titled “Don’t: Assume report order without documentation”walker:priv MyWalker { can work with Root entry { report ["item1", "item2"]; report {"count": 2}; }}
with entry { result = root spawn MyWalker();
# Bad: Magic indices data = result.reports[0]; meta = result.reports[1];
# Good: Document or structure clearly # reports[0]: List of items # reports[1]: Metadata object data = result.reports[0] if result.reports else []; meta = result.reports[1] if len(result.reports) > 1 else {};}