Object-Spatial Programming (OSP)#
Object-Spatial Programming is Jac's unique paradigm for working with graph-based data structures. It treats computation as mobile agents (walkers) that traverse a graph of data (nodes and edges).
The OSP Philosophy#
| Traditional Programming | Object-Spatial Programming |
|---|---|
| Bring data to code | Send code to data |
| Functions process data | Walkers visit nodes |
| Objects contain methods | Nodes have abilities that trigger on visits |
| Linear control flow | Graph-based traversal |
Nodes#
Nodes are data containers that form the vertices of your graph.
Basic Node Definition#
node Person {
has name: str;
has age: int = 0;
}
with entry {
# Create a node
alice = Person(name="Alice", age=30);
print(alice.name); # Alice
}
Node Abilities#
Nodes can have abilities that trigger when walkers visit them.
node Greeter {
has message: str = "Hello!";
# Triggers when ANY walker enters
can greet with entry {
print(self.message);
}
# Triggers only for specific walker type
can special_greet with VIPWalker entry {
print(f"VIP: {self.message}");
}
}
Edges#
Edges represent connections between nodes. They can be typed and hold data.
Basic Edge Definition#
Connection Operators#
| Operator | Description | Example |
|---|---|---|
++> |
Connect with generic edge | a ++> b |
+>:EdgeType:+> |
Connect with typed edge | a +>:Knows:+> b |
--> |
Traverse forward | [-->] |
<-- |
Traverse backward | [<--] |
<--> |
Traverse both directions | [<-->] |
Connecting Nodes#
node Person {
has name: str;
}
edge Knows {
has years: int = 1;
}
with entry {
# Create nodes connected to root
alice = root ++> Person(name="Alice");
bob = root ++> Person(name="Bob");
# Connect nodes with typed edge
alice[0] +>:Knows(years=5):+> bob[0];
}
Walkers#
Walkers are mobile agents that traverse the graph, carrying state and executing abilities.
Basic Walker Definition#
walker Greeter {
has greeting: str = "Hello";
has visited_count: int = 0;
can greet with Person entry {
print(f"{self.greeting}, {here.name}!");
self.visited_count += 1;
visit [-->]; # Continue to connected nodes
}
}
Spawning Walkers#
with entry {
root ++> Person(name="Alice");
root ++> Person(name="Bob");
# Spawn walker at root
walker_instance = Greeter() spawn root;
# Access walker state after traversal
print(f"Visited {walker_instance.visited_count} nodes");
}
Walker Abilities#
walker Explorer {
has path: list[str] = [];
# Trigger on root node
can start with `root entry {
print("Starting exploration");
visit [-->];
}
# Trigger on Person nodes
can explore with Person entry {
self.path.append(here.name);
visit [-->];
}
# Trigger when leaving any node
can log with exit {
print("Leaving a node");
}
}
Reference Keywords#
| Keyword | Context | Meaning |
|---|---|---|
self |
Walker/Node | The current walker or node instance |
here |
Walker ability | The current node being visited |
visitor |
Node ability | The walker currently visiting |
Using Reference Keywords#
node Room {
has name: str;
can welcome with Visitor entry {
# 'visitor' refers to the walker
print(f"Welcome {visitor.name} to {self.name}");
}
}
walker Visitor {
has name: str;
can explore with Room entry {
# 'here' refers to the current node
print(f"{self.name} entered {here.name}");
visit [-->];
}
}
Graph Traversal#
Visit Patterns#
walker Traverser {
can traverse with entry {
# Visit all forward connections
visit [-->];
# Visit all backward connections
visit [<--];
# Visit both directions
visit [<-->];
# Visit with else (no more nodes)
visit [-->] else {
print("End of path");
disengage;
}
}
}
Filtering Traversal#
walker Finder {
can find with entry {
# Visit only Person nodes
visit [-->(`?Person)];
# Visit via specific edge type
visit [-->:Knows:];
# Combined filtering
visit [-->:Knows:(`?Person)];
}
}
Disengage#
The disengage keyword stops a walker's traversal.
walker SearchWalker {
has target: str;
has found: bool = False;
can search with Person entry {
if here.name == self.target {
self.found = True;
disengage; # Stop traversing
}
visit [-->];
}
}
Complete Example#
"""Social network example demonstrating OSP concepts."""
node Person {
has name: str;
has messages: list[str] = [];
can receive with Messenger entry {
self.messages.append(visitor.message);
print(f"{self.name} received: {visitor.message}");
}
}
edge Knows {
has since: int = 2024;
}
walker Messenger {
has message: str;
has delivered_to: list[str] = [];
can start with `root entry {
visit [-->];
}
can deliver with Person entry {
self.delivered_to.append(here.name);
visit [-->:Knows:]; # Only traverse Knows edges
}
}
with entry {
# Build graph
alice = root ++> Person(name="Alice");
bob = root ++> Person(name="Bob");
carol = root ++> Person(name="Carol");
# Create relationships
alice[0] +>:Knows(since=2020):+> bob[0];
bob[0] +>:Knows(since=2022):+> carol[0];
# Send message
m = Messenger(message="Hello everyone!") spawn root;
print(f"Delivered to: {m.delivered_to}");
}
Common Patterns#
Tree Traversal#
walker TreeWalker {
has depth: int = 0;
can walk with entry {
print(f"{' ' * self.depth}Node at depth {self.depth}");
self.depth += 1;
visit [-->];
self.depth -= 1;
}
}
Data Collection#
walker Collector {
has data: list = [];
can start with `root entry {
visit [-->];
}
can collect with Person entry {
self.data.append({"name": here.name});
visit [-->];
}
}
with entry {
root ++> Person(name="Alice");
root ++> Person(name="Bob");
c = Collector() spawn root;
print(c.data); # [{"name": "Alice"}, {"name": "Bob"}]
}
Conditional Visiting#
walker AgeFilter {
has min_age: int = 18;
can start with `root entry {
visit [-->];
}
can filter with Person entry {
if here.age >= self.min_age {
print(f"{here.name} is {here.age}");
}
visit [-->];
}
}
Learn More#
| Topic | Resource |
|---|---|
| OSP Introduction | Chapter 8: OSP Paradigm |
| Nodes & Edges Detail | Chapter 9: Nodes and Edges |
| Walkers & Abilities | Chapter 10: Walkers |
| Advanced Traversal | Chapter 11: Advanced OSP |
| Formal Specification | Data-Spatial Programming |
| Nodes Reference | Nodes & Edges Guide |
| Walkers Reference | Walkers Guide |