Pipe-style function call#
Code Example
Runnable Example in Jac and JacLib
"""Pipe-style function call: Pipe-style function invocation."""
# ===== Part 1: Node and Walker Setup =====
node Task {
has name: str;
has priority: int = 0;
}
walker SimpleWalker {
has visited_names: list = [];
can start with `root entry {
print(f"SimpleWalker: Starting at root");
visit [-->];
}
can process with Task entry {
self.visited_names.append(here.name);
print(f" Processing: {here.name} (priority {here.priority})");
visit [-->];
}
}
# ===== Part 2: Basic spawn Keyword =====
walker BasicSpawn {
can start with `root entry {
print("BasicSpawn: started at root");
visit [-->];
}
can handle_task with Task entry {
print(f" BasicSpawn at: {here.name}");
visit [-->];
}
}
# ===== Part 3: Depth-First Spatial Call (:>) =====
walker DepthFirst {
has depth: int = 0;
can start with `root entry {
print("DepthFirst: root");
visit [-->];
}
can process with Task entry {
self.depth += 1;
print(f" Depth-first [{self.depth}]: {here.name}");
visit [-->];
}
}
# ===== Part 4: Breadth-First Spatial Call (|>) =====
walker BreadthFirst {
has order: list = [];
can start with `root entry {
print("BreadthFirst: root");
visit [-->];
}
can process with Task entry {
self.order.append(here.name);
print(f" Breadth-first: {here.name}");
visit [-->];
}
}
# ===== Part 5: Walker Return Values =====
walker DataCollector {
has collected: list = [];
has sum: int = 0;
can start with `root entry {
visit [-->];
}
can collect with Task entry {
self.collected.append(here.name);
self.sum += here.priority;
visit [-->];
}
}
# ===== Part 6: Walker Spawned from Nodes =====
walker NodeSpawner {
can start with `root entry {
visit [-->];
}
can process with Task entry {
print(f" At {here.name}");
if here.name == "Task2" {
print(" Spawning SubWalker from Task2");
# Spawn another walker from current node
here spawn SubWalker();
}
visit [-->];
}
}
walker SubWalker {
can start with Task entry {
print(f" SubWalker started at: {here.name}");
visit [-->];
}
can handle with Task entry {
print(f" SubWalker processing: {here.name}");
visit [-->];
}
}
# ===== Part 7: Walker Construction and Spawn Patterns =====
walker ConstructedWalker {
has label: str;
has max_visits: int = 5;
has visits: int = 0;
can start with `root entry {
print(f"ConstructedWalker '{self.label}' starting");
visit [-->];
}
can process with Task entry {
self.visits += 1;
print(f" [{self.label}] Visit {self.visits}: {here.name}");
if self.visits >= self.max_visits {
print(f" [{self.label}] Max visits reached");
disengage;
}
visit [-->];
}
}
# ===== Part 8: Multiple Walkers on Same Graph =====
walker Counter {
has total: int = 0;
can start with `root entry {
visit [-->];
}
can count_task with Task entry {
self.total += 1;
visit [-->];
}
}
walker Analyzer {
has high_priority: int = 0;
has low_priority: int = 0;
can start with `root entry {
visit [-->];
}
can analyze with Task entry {
if here.priority > 5 {
self.high_priority += 1;
} else {
self.low_priority += 1;
}
visit [-->];
}
}
# ===== Part 9: Walker Spawn Syntax Variations =====
walker SyntaxDemo {
can start with `root entry {
print("SyntaxDemo: Demonstrating all spawn syntaxes");
}
}
# ===== Execution and Tests =====
with entry {
print("=== 1. Building Test Graph ===");
# Build tree structure:
# root
# |
# Task1 (p=10)
# / \
# Task2 Task3 (p=3)
# (p=5) |
# Task4 (p=8)
task1 = Task(name="Task1", priority=10);
task2 = Task(name="Task2", priority=5);
task3 = Task(name="Task3", priority=3);
task4 = Task(name="Task4", priority=8);
root ++> task1;
task1 ++> task2;
task1 ++> task3;
task3 ++> task4;
print("Graph: root -> Task1 -> Task2");
print(" \\-> Task3 -> Task4\n");
print("=== 2. Basic spawn Keyword ===");
# Most common form: node spawn Walker()
root spawn BasicSpawn();
print("\n=== 3. Depth-First Spatial Call (:>) ===");
# Depth-first traversal: goes deep before wide
# Expected: Task1, Task2, Task3, Task4
root spawn :> DepthFirst;
print("\n=== 4. Breadth-First Spatial Call (|>) ===");
# Breadth-first traversal: goes level by level
# Expected: Task1, Task2, Task3, Task4
walker_bf = root spawn |> BreadthFirst;
print(f" Order visited: {walker_bf.order}");
print("\n=== 5. Walker Return Values ===");
# Walker instance is returned, can access state
collector = root spawn DataCollector();
print(f" Collected: {collector.collected}");
print(f" Sum of priorities: {collector.sum}");
print("\n=== 6. Walker Spawned from Nodes ===");
# Walkers can spawn other walkers from any node
root spawn NodeSpawner();
print("\n=== 7. Walker Construction with Arguments ===");
# Create walker with initial state
w1 = root spawn ConstructedWalker(label="Alpha", max_visits=2);
print(f" Walker visited {w1.visits} tasks");
print("\n=== 8. Multiple Walkers on Same Graph ===");
# Different walkers can analyze same graph structure
counter = root spawn Counter();
analyzer = root spawn Analyzer();
print(f" Counter: {counter.total} tasks");
print(f" Analyzer: {analyzer.high_priority} high, {analyzer.low_priority} low priority");
print("\n=== 9. Spawn Syntax Variations ===");
# Variation 1: Basic spawn
root spawn SyntaxDemo();
# Variation 2: Depth-first spawn
root spawn :> SyntaxDemo;
# Variation 3: Breadth-first spawn
root spawn |> SyntaxDemo;
# Variation 4: Walker-first syntax (less common)
SyntaxDemo() spawn root;
# Variation 5: With constructed walker
demo = SyntaxDemo();
root spawn demo;
print("\n=== 10. Traversal Strategy Comparison ===");
# Build a deeper tree to see difference
a = Task(name="A", priority=1);
b = Task(name="B", priority=2);
c = Task(name="C", priority=3);
d = Task(name="D", priority=4);
e = Task(name="E", priority=5);
root ++> a;
a ++> b;
a ++> c;
b ++> d;
b ++> e;
print("New graph: root -> A -> B -> D");
print(" \\ \\-> E");
print(" \\-> C\n");
print(" Depth-first (:>):");
df = root spawn :> SimpleWalker;
print(f" Visited: {df.visited_names}");
print("\n Breadth-first (|>):");
bf = root spawn |> SimpleWalker;
print(f" Visited: {bf.visited_names}");
print("\n✓ Object spatial calls demonstrated!");
}
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 |
|
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
Object spatial calls are the mechanisms for spawning and executing walkers on graph nodes. The spawn
operator creates walker instances and launches them at specified nodes, enabling the "computation flows to data" paradigm.
What is Walker Spawning?
Spawning a walker means creating a walker instance and starting its execution at a specific node in the graph. Think of it like deploying an agent that will traverse your data structure, performing operations at each location it visits.
Basic spawn Syntax
The most common form is shown on line 204: root spawn BasicSpawn()
.
This breaks down as:
- root
- the starting node (where the walker begins)
- spawn
- the operator that launches the walker
- BasicSpawn()
- creates a new walker instance
When this executes:
1. A new BasicSpawn
walker is created
2. The walker starts at the root node
3. The walker's matching entry ability executes (lines 26-28)
4. The walker processes its visit queue until empty
Understanding the Visit Queue
Walkers maintain a FIFO (first-in-first-out) queue of nodes to visit. This is fundamental to understanding how walkers traverse graphs:
graph TD
A[Walker spawns at root] --> B[Root entry ability executes]
B --> C[visit adds nodes to queue]
C --> D[Process first node in queue]
D --> E[Node ability executes]
E --> F{More in queue?}
F -->|Yes| D
F -->|No| G[Walker completes]
The visit queue always processes nodes in FIFO order - first queued, first visited. Lines 17-21 show this pattern: visit [-->]
adds all outgoing nodes to the queue, and they're processed in the order they were added.
How visit Statements Control Traversal
Lines 14, 20, 28, 33 all use visit [-->]
to queue nodes. The visit statement determines which nodes get added to the queue:
Visit Expression | What Gets Queued | Example Line |
---|---|---|
visit [-->]; |
All outgoing nodes | 14, 20, 28 |
visit [->:Type:->]; |
Outgoing nodes via Type edges | - |
visit [<--]; |
All incoming nodes | - |
visit node_list; |
Specific nodes | - |
Once nodes are in the queue, they're always processed FIFO regardless of how they were added.
Walker Return Values
Lines 219-221 demonstrate accessing walker state after execution. The spawn operator returns the walker instance, allowing you to:
- Access accumulated data (lines 71-72: collected
and sum
attributes)
- Check walker state after traversal completes
- Use walkers as data collectors or analyzers
Lines 70-83 define DataCollector
, which accumulates data during traversal (lines 79-80). After spawning (line 219), the walker's attributes remain accessible.
Spawn Syntax Variations
Lines 240-255 demonstrate all valid spawn syntaxes:
Syntax | Description | Example Line |
---|---|---|
node spawn Walker() |
Most common form | 242 |
node spawn :> Walker |
With :> operator, walker type |
245 |
node spawn |> Walker |
With |> operator, walker type |
248 |
Walker() spawn node |
Walker-first syntax | 251 |
Pre-constructed | Create walker, then spawn | 254-255 |
Important: The :>
and |>
operators take the walker TYPE, not a constructed instance. Use root spawn :> Walker
, NOT root spawn :> Walker()
.
Walker Spawned from Nodes
Lines 85-112 show walkers spawning other walkers. Line 96 demonstrates: here spawn SubWalker()
.
The execution flow:
1. NodeSpawner
visits Task2 (line 92)
2. Line 96: A new SubWalker
spawns at the current node (here
)
3. The SubWalker
starts its own traversal from Task2
4. Both walkers can run independently
graph LR
R[Root] --> T1[Task1]
T1 --> T2[Task2]
T1 --> T3[Task3]
T3 --> T4[Task4]
style T2 fill:#c62828,stroke:#fff,color:#fff
Note[SubWalker spawns here at Task2]
This enables hierarchical processing where parent walkers spawn child walkers at interesting nodes.
Walker Construction with Arguments
Lines 114-136 show parameterized walkers. Line 229 demonstrates: root spawn ConstructedWalker(label="Alpha", max_visits=2)
.
The walker is initialized with:
- label="Alpha"
sets the walker's label (line 116)
- max_visits=2
limits how many nodes it visits (line 117)
Lines 125-135 show the walker using these parameters to control its behavior, stopping after reaching max_visits
via disengage
(line 131).
Multiple Walkers on Same Graph
Lines 232-238 demonstrate running different walkers on the same graph structure. Each walker traverses independently:
- Counter
(lines 139-150) counts total tasks
- Analyzer
(lines 152-168) categorizes by priority
This demonstrates separation of data and computation - the graph structure is static, but different walkers extract different information.
Building Graph Structures for Traversal
Lines 179-200 build a test graph structure:
graph TD
Root --> T1[Task1 p=10]
T1 --> T2[Task2 p=5]
T1 --> T3[Task3 p=3]
T3 --> T4[Task4 p=8]
Lines 194-197 create the edges using the ++>
operator:
- Line 194: root ++> task1
connects root to Task1
- Line 195: task1 ++> task2
connects Task1 to Task2
- Lines 196-197: Create the remaining connections
This structure is then traversed by various walkers throughout the example.
Comparing Traversal Approaches
Lines 257-281 demonstrate how different visit patterns affect traversal order. The key insight: traversal order is controlled by HOW you structure your visit statements, not by the spawn operator used.
Both depth-first and breadth-first styles process the queue FIFO, but they add nodes to the queue differently: - Depth-first effect: Add child nodes to front of queue - Breadth-first effect: Add child nodes to end of queue (default)
Spawn Points and Starting Context
Walkers can spawn from different locations:
Spawn Point | Usage | Example Line |
---|---|---|
root spawn W() |
Start at root node | 204, 214, 219 |
here spawn W() |
Start at current node | 96 |
node_var spawn W() |
Start at specific node | - |
The spawn point becomes the walker's initial here
reference, and the matching entry ability executes for that node type.
The Walker Execution Model
When a walker spawns, this sequence occurs:
- Initialization: Walker instance created (if using
Walker()
syntax) - Entry ability: Matching entry ability for spawn node type executes
- Visit queue: Empty FIFO queue created
- Queue population:
visit
statements add nodes to queue - FIFO processing: Walker processes queued nodes in order
- Node abilities: For each visited node:
- Walker's matching entry abilities execute
- Node's abilities (if any) execute
- More nodes may be queued via
visit
statements - Completion: When queue empties, exit abilities execute
- Return: Walker instance returned to spawn caller
Practical Patterns
Pattern 1: Data Collection (lines 70-83, 219-221)
Pattern 2: Graph Search (stopping early with disengage)
Pattern 3: Multi-Walker Analysis (lines 232-238)
Pattern 4: Hierarchical Processing (lines 85-112)
Common Mistakes to Avoid
- Wrong operator syntax:
- ✗ Wrong:
root spawn :> Walker()
-
✓ Right:
root spawn :> Walker
-
Assuming operators control traversal:
- ✗ Wrong assumption:
:>
does depth-first automatically -
✓ Reality: Queue is FIFO; control traversal with visit logic
-
Forgetting visit statements:
- Without
visit [-->]
, walker only executes on spawn node -
No visits = no traversal
-
Not capturing return value:
- ✗
root spawn Collector();
loses walker state - ✓
result = root spawn Collector();
can access collected data
The Computation-to-Data Paradigm
Object spatial calls invert traditional programming:
Traditional approach (data comes to computation):
OSP approach (computation goes to data):
This inversion enables: - Locality: Computation executes where data resides - Persistence: Graph structure persists via root connectivity - Distribution: Walkers can traverse distributed graphs - Decoupling: Walker logic independent of graph structure - Reusability: Same walker works on different graph topologies
Object spatial calls are the fundamental execution primitive of Jac's Object-Spatial Programming model, enabling mobile computation that flows through persistent graph structures.