Walker visit and disengage (OSP)#
Code Example
Runnable Example in Jac and JacLib
"""Walker visit and disengage (OSP): Graph traversal control with visit and disengage statements."""
node Person {
has name: str;
}
edge Friend {}
edge Colleague {
has strength: int;
}
# ========================================
# BASIC VISIT STATEMENTS
# ========================================
# Simple visit to outgoing edges
walker BasicVisitor {
can start with `root entry {
print("BasicVisitor: visiting outgoing edges");
visit [-->];
}
can visit_person with Person entry {
print(f"BasicVisitor: at {here.name}");
}
}
# ========================================
# VISIT WITH ELSE CLAUSE
# ========================================
# Visit with else handles no edges case
walker VisitWithElse {
can start with `root entry {
print("VisitWithElse: visiting with else clause");
visit [-->] else {
print("VisitWithElse: no outgoing edges from root");
}
}
can visit_person with Person entry {
print(f"VisitWithElse: at {here.name}");
visit [-->] else {
print(f"VisitWithElse: leaf node - {here.name}");
}
}
}
# ========================================
# DIRECT NODE VISIT
# ========================================
# Visit specific node directly
walker DirectVisit {
has target: Person;
can start with `root entry {
print(f"DirectVisit: going directly to target");
visit self.target;
}
can at_target with Person entry {
print(f"DirectVisit: arrived at {here.name}");
}
}
# ========================================
# TYPED EDGE VISIT
# ========================================
# Visit only specific edge types
walker TypedVisit {
can start with Person entry {
print(f"TypedVisit: at {here.name}, visiting Friend edges only");
visit [->:Friend:->];
}
can visit_friend with Person entry {
print(f"TypedVisit: visited friend {here.name}");
}
}
# ========================================
# FILTERED VISIT WITH EDGE ATTRIBUTES
# ========================================
# Visit edges filtered by attributes
walker FilteredVisit {
can start with Person entry {
print(f"FilteredVisit: visiting strong colleagues from {here.name}");
visit [->:Colleague:strength > 5:->];
}
can visit_colleague with Person entry {
print(f"FilteredVisit: visited colleague {here.name}");
}
}
# ========================================
# BASIC DISENGAGE
# ========================================
# Disengage terminates walker immediately
walker BasicDisengage {
can start with `root entry {
print("BasicDisengage: starting traversal");
visit [-->];
}
can visit_person with Person entry {
print(f"BasicDisengage: at {here.name}");
if here.name == "Bob" {
print("BasicDisengage: found Bob, disengaging");
disengage;
}
print(f"BasicDisengage: continuing from {here.name}");
visit [-->];
}
}
# ========================================
# CONDITIONAL DISENGAGE
# ========================================
# Disengage based on walker state
walker ConditionalDisengage {
has max_visits: int = 2;
has visit_count: int = 0;
can start with `root entry {
print("ConditionalDisengage: starting");
visit [-->];
}
can count_visits with Person entry {
self.visit_count += 1;
print(f"ConditionalDisengage: visit {self.visit_count} at {here.name}");
if self.visit_count >= self.max_visits {
print("ConditionalDisengage: max visits reached, disengaging");
disengage;
}
visit [-->];
}
}
# ========================================
# SEARCH WITH DISENGAGE
# ========================================
# Search for target and disengage when found
walker SearchWalker {
has target_name: str;
has found: bool = False;
can search with `root entry {
print(f"SearchWalker: searching for {self.target_name}");
visit [-->];
}
can check with Person entry {
print(f"SearchWalker: checking {here.name}");
if here.name == self.target_name {
print(f"SearchWalker: found {here.name}!");
self.found = True;
disengage;
}
visit [-->];
}
}
# ========================================
# MULTIPLE VISIT PATTERNS
# ========================================
# Walker with multiple visit strategies
walker MultiVisit {
has visit_phase: int = 1;
can start with `root entry {
print("MultiVisit: phase 1 - visit all outgoing");
visit [-->];
}
can phase_one with Person entry {
if self.visit_phase == 1 {
print(f"MultiVisit: phase 1 at {here.name}");
self.visit_phase = 2;
visit [-->];
}
}
can phase_two with Person entry {
if self.visit_phase == 2 {
print(f"MultiVisit: phase 2 at {here.name}");
}
}
}
# ========================================
# DISENGAGE STOPS CODE AFTER IT
# ========================================
# Demonstrate that disengage stops execution immediately
walker ImmediateStop {
can self_destruct with `root entry {
print("ImmediateStop: before disengage");
disengage;
print("ImmediateStop: after disengage (never printed)");
}
}
# ========================================
# COMBINED VISIT AND DISENGAGE
# ========================================
# Complex traversal with both visit and disengage
walker ComplexTraversal {
has depth: int = 0;
has max_depth: int = 2;
has nodes_visited: list[str] = [];
can start with `root entry {
print("ComplexTraversal: starting from root");
visit [-->];
}
can traverse with Person entry {
self.depth += 1;
self.nodes_visited.append(here.name);
print(f"ComplexTraversal: depth {self.depth} at {here.name}");
if self.depth >= self.max_depth {
print(f"ComplexTraversal: max depth reached, disengaging");
disengage;
}
visit [-->] else {
print(f"ComplexTraversal: leaf node at {here.name}");
}
}
}
# ========================================
# GRAPH SETUP AND WALKER EXECUTION
# ========================================
with entry {
# Build graph structure
alice = Person(name="Alice");
bob = Person(name="Bob");
charlie = Person(name="Charlie");
diana = Person(name="Diana");
eve = Person(name="Eve");
# Create graph connections
root ++> alice;
alice ++> bob;
alice ++> charlie;
bob ++> diana;
charlie ++> eve;
# Add typed edges
alice +>:Friend:+> bob;
alice +>:Colleague(strength=7):+> charlie;
alice +>:Colleague(strength=3):+> diana;
print("=== Basic Visit ===");
root spawn BasicVisitor();
print("\n=== Visit with Else ===");
root spawn VisitWithElse();
print("\n=== Direct Visit ===");
root spawn DirectVisit(target=charlie);
print("\n=== Typed Visit ===");
alice spawn TypedVisit();
print("\n=== Filtered Visit ===");
alice spawn FilteredVisit();
print("\n=== Basic Disengage ===");
root spawn BasicDisengage();
print("\n=== Conditional Disengage ===");
root spawn ConditionalDisengage();
print("\n=== Search Walker ===");
w = root spawn SearchWalker(target_name="Charlie");
print(f"Found: {w.found}");
print("\n=== Multi Visit ===");
root spawn MultiVisit();
print("\n=== Immediate Stop ===");
root spawn ImmediateStop();
print("\n=== Complex Traversal ===");
ct = root spawn ComplexTraversal();
print(f"Nodes visited: {ct.nodes_visited}");
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
|
Jac Grammar Snippet
Description
Walker Visit and Disengage (OSP)
Walker visit and disengage statements are core control mechanisms in Jac's Object Spatial Programming (OSP) model. The visit
statement directs walkers to traverse to nodes in the graph, while disengage
immediately terminates the walker's execution. Together, they enable sophisticated graph traversal patterns for spatial computation.
What are Walker Visit and Disengage Statements?
In OSP, walkers are autonomous agents that traverse graph structures, executing code at each node they visit. The visit and disengage statements control this traversal:
visit
: Directs the walker to move to specific nodes based on edge patterns or expressionsdisengage
: Immediately stops the walker, terminating all further traversal and code execution
These statements work together to implement graph algorithms, searches, data collection, and distributed computation patterns.
Why Combine Visit and Disengage?
Visit and disengage are complementary operations that form the foundation of walker control flow:
visit
enables forward progress through the graphdisengage
provides early termination and exit conditions- Together they implement complex traversal strategies (DFS, BFS, search, filtering)
Understanding both is essential for effective spatial programming in Jac.
VISIT STATEMENTS#
The visit
statement moves a walker to new nodes in the graph, where it can execute abilities.
Basic Visit Syntax#
The expression can be:
- An edge filter pattern (e.g., [-->]
)
- A specific node reference (e.g., self.target
)
- A node collection or list
Basic Visit to Outgoing Edges (lines 17-27)
Lines 17-27 demonstrate the simplest visit pattern:
walker BasicVisitor {
can start with `root entry {
print("BasicVisitor: visiting outgoing edges");
visit [-->];
}
}
Line 21 uses visit [-->]
to traverse to all nodes connected by outgoing edges. The walker's visit_person
ability (line 24) then executes at each Person node.
How Visit Works:
1. Walker starts at root node
2. visit [-->]
finds all outgoing edges
3. Walker clones and moves to each connected node
4. Node-specific abilities execute at each destination
5. Traversal continues from each new location
Visit with Else Clause#
The else clause handles the case when no nodes match the visit pattern.
Syntax:
Visit with Else (lines 33-49)
Lines 33-49 show how to handle empty edge sets:
walker VisitWithElse {
can start with `root entry {
visit [-->] else {
print("VisitWithElse: no outgoing edges from root");
}
}
}
Line 36: visit [-->] else { ... }
- if no outgoing edges exist, the else block executes instead.
Line 45-47: At leaf nodes (nodes with no outgoing edges), the else clause detects the end of a branch.
Use Cases: - Detecting leaf nodes in a tree - Handling empty graph sections - Default behavior when no edges match filters
Direct Node Visit#
Visit a specific node directly, regardless of graph structure.
Direct Visit (lines 55-67)
Lines 55-67 demonstrate visiting a specific node:
Line 61: visit self.target
moves directly to the target node stored in the walker's field. This bypasses normal graph traversal - the walker teleports to the specific node.
When to Use Direct Visit: - Jump to a known node without traversing edges - Implement teleportation or fast-travel patterns - Visit nodes based on walker state or computation results
Typed Edge Visit#
Filter visits to only specific edge types.
Typed Visit (lines 73-84)
Lines 73-84 show type-based edge filtering:
Line 76: visit [->:Friend:->]
only traverses Friend
edges, ignoring all other edge types.
Edge Type Syntax:
- [->:EdgeType:->]
- edges of specific type
- [-->]
- all outgoing edges (any type)
- [<--]
- all incoming edges
- [<->]
- all edges (bidirectional)
This enables selective traversal based on relationship types (friends, colleagues, dependencies, etc.).
Filtered Visit with Edge Attributes#
Visit edges based on attribute conditions.
Filtered Visit (lines 90-101)
Lines 90-101 demonstrate attribute-based filtering:
Line 94: visit [->:Colleague:strength > 5:->]
only traverses Colleague
edges where the strength
attribute is greater than 5.
Filter Syntax:
Where condition
can be any boolean expression using edge attributes:
- strength > 5
- weight <= 10
- active == True
- created_at > some_date
Use Cases: - Finding strong connections - Weight-based graph algorithms - Filtering by relationship properties
DISENGAGE STATEMENTS#
The disengage
statement immediately terminates the walker's execution.
Basic Disengage Syntax#
When executed, disengage: 1. Stops the current ability immediately 2. Prevents any code after the disengage from running 3. Terminates the walker completely - no further nodes are visited 4. Returns control (the walker is done)
Basic Disengage (lines 107-126)
Lines 107-126 show simple disengage usage:
walker BasicDisengage {
can visit_person with Person entry {
print(f"BasicDisengage: at {here.name}");
if here.name == "Bob" {
print("BasicDisengage: found Bob, disengaging");
disengage;
}
print(f"BasicDisengage: continuing from {here.name}");
visit [-->];
}
}
Lines 118-120: When the walker finds Bob, it disengages. Line 122 never executes for Bob because disengage stops execution immediately.
Key Point: Line 123 (visit [-->]
) also doesn't execute when disengaged, so no further traversal happens.
Conditional Disengage#
Disengage based on walker state or computation.
Conditional Disengage (lines 132-153)
Lines 132-153 implement count-based termination:
walker ConditionalDisengage {
has max_visits: int = 2;
has visit_count: int = 0;
can count_visits with Person entry {
self.visit_count += 1;
if self.visit_count >= self.max_visits {
disengage;
}
visit [-->];
}
}
Line 145: Tracks how many nodes have been visited Line 147-149: Disengages after reaching max visits Line 150: Only executes if max not reached
This pattern implements early termination based on walker state.
Search with Disengage#
Use disengage to exit as soon as a target is found.
Search Walker (lines 159-181)
Lines 159-181 demonstrate search-and-terminate:
walker SearchWalker {
has target_name: str;
has found: bool = False;
can check with Person entry {
if here.name == self.target_name {
self.found = True;
disengage;
}
visit [-->];
}
}
Lines 173-176: When target is found, set flag and disengage immediately Line 177: Only continues searching if target not found yet
Optimization: Disengage prevents wasted traversal after finding the target. Without it, the walker would visit every node in the graph.
Immediate Code Termination#
Disengage stops code execution at the exact line it's called.
Immediate Stop (lines 203-212)
Lines 203-212 demonstrate execution stopping:
walker ImmediateStop {
can self_destruct with `root entry {
print("ImmediateStop: before disengage");
disengage;
print("ImmediateStop: after disengage (never printed)");
}
}
Line 207: This prints Line 208: Disengage executes here Line 209: Never executes - code after disengage is unreachable
This is similar to return
in functions, but for walkers.
COMBINED VISIT AND DISENGAGE PATTERNS#
Real-world walker logic combines both visit and disengage for complex traversal.
Multiple Visit Strategies (lines 187-201)
Lines 187-201 show a walker with multiple phases:
walker MultiVisit {
has visit_phase: int = 1;
can phase_one with Person entry {
if self.visit_phase == 1 {
self.visit_phase = 2;
visit [-->];
}
}
can phase_two with Person entry {
if self.visit_phase == 2 {
print(f"MultiVisit: phase 2 at {here.name}");
}
}
}
The walker changes strategy based on internal state, visiting different nodes in different ways.
Complex Traversal (lines 218-247)
Lines 218-247 combine multiple concepts:
walker ComplexTraversal {
has depth: int = 0;
has max_depth: int = 2;
can traverse with Person entry {
self.depth += 1;
if self.depth >= self.max_depth {
disengage;
}
visit [-->] else {
print(f"leaf node at {here.name}");
}
}
}
This walker implements depth-limited traversal with: - Depth tracking (line 228) - Depth-based disengage (lines 231-233) - Else clause for leaf detection (lines 235-237)
VISIT VS DISENGAGE: KEY DIFFERENCES#
Feature | visit |
disengage |
---|---|---|
Purpose | Move to new nodes | Stop walker completely |
Effect | Continues traversal | Ends traversal |
Scope | Creates new contexts at destinations | Terminates current context |
Code after | Statement after visit may execute | Code after disengage never runs |
Use case | Progress through graph | Exit conditions, early termination |
Important: Disengage is final - once called, the walker cannot visit more nodes.
TRAVERSAL PATTERNS#
Breadth-First Search (BFS)#
Use visit [-->]
to visit all neighbors before going deeper. Walkers naturally implement BFS by visiting all edges at once.
Depth-First Search (DFS)#
Visit one edge at a time, storing others for later. Combine with walker state to track path.
Search and Exit#
Use visit
to explore + disengage
when found (lines 159-181).
Bounded Traversal#
Track depth/count and disengage
at limit (lines 218-247).
Filtered Traversal#
Use typed edges or attribute filters to visit only relevant nodes (lines 73-101).
EXECUTION MODEL#
Visit Execution: 1. Evaluate the visit expression 2. Find all matching nodes/edges 3. For each match: - Clone walker state - Move walker to node - Execute node-specific ability - Continue from new location
Disengage Execution: 1. Stop current ability immediately 2. Discard walker instance 3. Return control to spawn point 4. Walker object remains accessible but inactive
Graph Setup (lines 252-271)
Lines 252-271 build the test graph: - Creates 5 Person nodes (Alice, Bob, Charlie, Diana, Eve) - Connects them with various edges - Adds typed edges (Friend, Colleague) with attributes - Demonstrates different edge structures for testing
Walker Execution (lines 273-302)
Lines 273-302 spawn walkers to demonstrate all patterns:
- Each print statement marks a new example
- Walkers execute independently
- Output shows traversal behavior
- Return values (like w.found
) show walker state after completion
WHEN TO USE VISIT AND DISENGAGE#
Use Visit When: - Traversing graph structures - Implementing graph algorithms - Distributed computation across nodes - Collecting data from multiple locations - Following relationships between entities
Use Disengage When: - Search target is found - Resource limits are reached (depth, count, time) - Error conditions occur - Computation is complete - Early exit optimizations are needed
Use Together When: - Implementing search algorithms - Bounded or limited traversal - Conditional exploration - Resource-aware computation - Complex multi-phase traversal
BEST PRACTICES#
- Always handle leaf nodes: Use
else
clauses to detect when no edges exist - Limit traversal: Use disengage with depth/count limits to prevent infinite traversal
- Use typed edges: Filter by edge type to traverse only relevant relationships
- Track walker state: Store visited nodes, depth, results in walker fields
- Disengage early: Exit as soon as goal is met to save computation
- Test edge cases: Empty graphs, single nodes, cycles, disconnected components
- Document traversal logic: Complex visit patterns benefit from comments
Common Pitfall: Forgetting to disengage in searches leads to traversing the entire graph even after finding the target.
Performance Tip: Use edge filters ([->:Type:condition:->]
) to minimize unnecessary visits.
COMPARISON WITH TRADITIONAL PROGRAMMING#
Visit vs For Loop: - Visit: Declarative spatial traversal - For loop: Imperative iteration over collections
Disengage vs Return: - Disengage: Terminates entire walker (all pending visits) - Return: Exits current function only
OSP Advantages: - Natural graph representation - Automatic parallelization potential - Declarative relationship traversal - Built-in spatial semantics
The combination of visit and disengage makes OSP uniquely suited for graph-based computation, spatial data processing, and distributed algorithms.