Skip to content

Connect expressions (OSP)#

Code Example

Runnable Example in Jac and JacLib

"""Connect expressions (OSP): Graph edge creation and connection operators."""

# ===== Node and Edge Definitions =====
node Person {
    has name: str;
    has age: int = 0;
}

node City {
    has name: str;
}

edge LivesIn {
    has years: int = 0;
}

edge Friend {
    has since: int = 2020;
}

edge Colleague {
    has department: str;
}

# ===== Test Walker =====
walker GraphBuilder {
    can build with `root entry {
        print("=== 1. Untyped Connect Operators ===");
        # Forward (++>), Backward (<++), Bidirectional (<++>)
        alice = Person(name="Alice", age=30);
        bob = Person(name="Bob", age=25);
        charlie = Person(name="Charlie", age=28);

        alice ++> bob;  # Forward: alice -> bob
        print(f"Forward: {alice.name} ++> {bob.name}");

        bob <++ charlie;  # Backward: charlie -> bob
        print(f"Backward: {bob.name} <++ {charlie.name} (edge from Charlie to Bob)");

        alice <++> charlie;  # Bidirectional: alice <-> charlie
        print(f"Bidirectional: {alice.name} <++> {charlie.name} (both directions)");

        print("\n=== 2. Typed Connect Operators ===");
        # Forward (+>:Type:+>), Backward (<+:Type:<+), Bidirectional (<+:Type:+>)
        diana = Person(name="Diana", age=32);
        eve = Person(name="Eve", age=27);
        frank = Person(name="Frank", age=29);
        nyc = City(name="New York");
        london = City(name="London");

        diana +>:LivesIn:+> nyc;  # Typed forward
        print(f"Typed forward: {diana.name} +>:LivesIn:+> {nyc.name}");

        eve <+:LivesIn:<+ london;  # Typed backward: london -> eve
        print(f"Typed backward: {eve.name} <+:LivesIn:<+ {london.name} (edge from London to Eve)");

        diana <+:Friend:+> eve;  # Typed bidirectional
        print(f"Typed bidirectional: {diana.name} <+:Friend:+> {eve.name}");

        print("\n=== 3. Edge Attribute Initialization ===");
        # Initialize edge attributes during connect (all directions)
        grace = Person(name="Grace", age=26);
        henry = Person(name="Henry", age=31);
        iris = Person(name="Iris", age=24);

        grace +>: Friend(since=2015) :+> henry;  # Forward with attributes
        print(f"Forward with attrs: {grace.name} +>: Friend(since=2015) :+> {henry.name}");

        henry <+: Friend(since=2018) :<+ iris;  # Backward with attributes
        print(f"Backward with attrs: {henry.name} <+: Friend(since=2018) :<+ {iris.name}");

        grace <+: Colleague(department="Engineering") :+> iris;  # Bidirectional with attributes
        print(f"Bidirectional with attrs: {grace.name} <+: Colleague(department='Engineering') :+> {iris.name}");

        print("\n=== 4. Chained Connections ===");
        # Multiple connects in one expression
        jack = Person(name="Jack", age=35);
        kate = Person(name="Kate", age=29);
        liam = Person(name="Liam", age=30);
        mike = Person(name="Mike", age=33);

        jack ++> kate ++> liam ++> mike;  # Chain of connections
        print(f"Chain: {jack.name} ++> {kate.name} ++> {liam.name} ++> {mike.name}");

        print("\n=== 5. Inline Node Creation ===");
        # Create nodes inline within connect expression
        nina = Person(name="Nina", age=28);

        nina ++> Person(name="InlineNode1", age=35);  # Untyped with inline
        nina +>:Friend:+> Person(name="InlineNode2", age=40);  # Typed with inline
        nina +>: Friend(since=2010) :+> Person(name="InlineNode3", age=45);  # With attributes
        print("Connected to 3 inline-created nodes (untyped, typed, with attrs)");

        print("\n=== 6. Connect to Multiple Targets ===");
        # One node connecting to multiple targets
        oscar = Person(name="Oscar", age=27);
        paula = Person(name="Paula", age=26);
        quinn = Person(name="Quinn", age=24);

        oscar ++> paula;
        oscar ++> quinn;
        oscar +>:Friend:+> Person(name="Rita", age=30);
        print(f"Connected {oscar.name} to 3 different targets");

        print("\n=== 7. Disconnect Operator ===");
        # Grammar: disconnect_op: KW_DELETE edge_op_ref
        # Syntax: node del [-->] target
        print("Disconnect syntax: node del [-->] target (deletes edges from node to target)");

        print("\n=== 8. Connect in Expressions ===");
        # Connect expressions return values and can be used in larger expressions
        steve = Person(name="Steve", age=45);
        tina = Person(name="Tina", age=42);

        steve ++> tina;
        print(f"Connect used in expression: {steve.name} ++> {tina.name}");

        print("\n✓ All connect expression features demonstrated!");
        disengage;
    }
}

# ===== Edge Traversal Demonstration =====
walker EdgeTraverser {
    can traverse with `root entry {
        print("\n=== Edge Traversal with Visit ===");

        # Build a small graph demonstrating typed edges
        a = Person(name="A", age=25);
        b = Person(name="B", age=30);
        c = Person(name="C", age=35);

        root ++> a;
        a +>: Friend(since=2010) :+> b;
        a +>: Colleague(department="Sales") :+> c;
        b +>: Friend(since=2015) :+> c;

        print("Graph: root->A, A-Friend->B, A-Colleague->C, B-Friend->C");
        print("Visiting all outgoing edges from root:");
        visit [-->];
    }

    can traverse with Person entry {
        print(f"  Visited: {here.name}, age={here.age}");
    }
}

# ===== Execution =====
with entry {
    root spawn GraphBuilder();
    root spawn EdgeTraverser();
}
"""Connect expressions (OSP): Graph edge creation and connection operators."""

# ===== Node and Edge Definitions =====
node Person {
    has name: str;
    has age: int = 0;
}

node City {
    has name: str;
}

edge LivesIn {
    has years: int = 0;
}

edge Friend {
    has since: int = 2020;
}

edge Colleague {
    has department: str;
}

# ===== Test Walker =====
walker GraphBuilder {
    can build with `root entry {
        print("=== 1. Untyped Connect Operators ===");
        # Forward (++>), Backward (<++), Bidirectional (<++>)
        alice = Person(name="Alice", age=30);
        bob = Person(name="Bob", age=25);
        charlie = Person(name="Charlie", age=28);

        alice ++> bob;  # Forward: alice -> bob
        print(f"Forward: {alice.name} ++> {bob.name}");

        bob <++ charlie;  # Backward: charlie -> bob
        print(f"Backward: {bob.name} <++ {charlie.name} (edge from Charlie to Bob)");

        alice <++> charlie;  # Bidirectional: alice <-> charlie
        print(f"Bidirectional: {alice.name} <++> {charlie.name} (both directions)");

        print("\n=== 2. Typed Connect Operators ===");
        # Forward (+>:Type:+>), Backward (<+:Type:<+), Bidirectional (<+:Type:+>)
        diana = Person(name="Diana", age=32);
        eve = Person(name="Eve", age=27);
        frank = Person(name="Frank", age=29);
        nyc = City(name="New York");
        london = City(name="London");

        diana +>:LivesIn:+> nyc;  # Typed forward
        print(f"Typed forward: {diana.name} +>:LivesIn:+> {nyc.name}");

        eve <+:LivesIn:<+ london;  # Typed backward: london -> eve
        print(f"Typed backward: {eve.name} <+:LivesIn:<+ {london.name} (edge from London to Eve)");

        diana <+:Friend:+> eve;  # Typed bidirectional
        print(f"Typed bidirectional: {diana.name} <+:Friend:+> {eve.name}");

        print("\n=== 3. Edge Attribute Initialization ===");
        # Initialize edge attributes during connect (all directions)
        grace = Person(name="Grace", age=26);
        henry = Person(name="Henry", age=31);
        iris = Person(name="Iris", age=24);

        grace +>: Friend(since=2015) :+> henry;  # Forward with attributes
        print(f"Forward with attrs: {grace.name} +>: Friend(since=2015) :+> {henry.name}");

        henry <+: Friend(since=2018) :<+ iris;  # Backward with attributes
        print(f"Backward with attrs: {henry.name} <+: Friend(since=2018) :<+ {iris.name}");

        grace <+: Colleague(department="Engineering") :+> iris;  # Bidirectional with attributes
        print(f"Bidirectional with attrs: {grace.name} <+: Colleague(department='Engineering') :+> {iris.name}");

        print("\n=== 4. Chained Connections ===");
        # Multiple connects in one expression
        jack = Person(name="Jack", age=35);
        kate = Person(name="Kate", age=29);
        liam = Person(name="Liam", age=30);
        mike = Person(name="Mike", age=33);

        jack ++> kate ++> liam ++> mike;  # Chain of connections
        print(f"Chain: {jack.name} ++> {kate.name} ++> {liam.name} ++> {mike.name}");

        print("\n=== 5. Inline Node Creation ===");
        # Create nodes inline within connect expression
        nina = Person(name="Nina", age=28);

        nina ++> Person(name="InlineNode1", age=35);  # Untyped with inline
        nina +>:Friend:+> Person(name="InlineNode2", age=40);  # Typed with inline
        nina +>: Friend(since=2010) :+> Person(name="InlineNode3", age=45);  # With attributes
        print("Connected to 3 inline-created nodes (untyped, typed, with attrs)");

        print("\n=== 6. Connect to Multiple Targets ===");
        # One node connecting to multiple targets
        oscar = Person(name="Oscar", age=27);
        paula = Person(name="Paula", age=26);
        quinn = Person(name="Quinn", age=24);

        oscar ++> paula;
        oscar ++> quinn;
        oscar +>:Friend:+> Person(name="Rita", age=30);
        print(f"Connected {oscar.name} to 3 different targets");

        print("\n=== 7. Disconnect Operator ===");
        # Grammar: disconnect_op: KW_DELETE edge_op_ref
        # Syntax: node del [-->] target
        print("Disconnect syntax: node del [-->] target (deletes edges from node to target)");

        print("\n=== 8. Connect in Expressions ===");
        # Connect expressions return values and can be used in larger expressions
        steve = Person(name="Steve", age=45);
        tina = Person(name="Tina", age=42);

        steve ++> tina;
        print(f"Connect used in expression: {steve.name} ++> {tina.name}");

        print("\n✓ All connect expression features demonstrated!");
        disengage;
    }
}

# ===== Edge Traversal Demonstration =====
walker EdgeTraverser {
    can traverse with `root entry {
        print("\n=== Edge Traversal with Visit ===");

        # Build a small graph demonstrating typed edges
        a = Person(name="A", age=25);
        b = Person(name="B", age=30);
        c = Person(name="C", age=35);

        root ++> a;
        a +>: Friend(since=2010) :+> b;
        a +>: Colleague(department="Sales") :+> c;
        b +>: Friend(since=2015) :+> c;

        print("Graph: root->A, A-Friend->B, A-Colleague->C, B-Friend->C");
        print("Visiting all outgoing edges from root:");
        visit [-->];
    }

    can traverse with Person entry {
        print(f"  Visited: {here.name}, age={here.age}");
    }
}

# ===== Execution =====
with entry {
    root spawn GraphBuilder();
    root spawn EdgeTraverser();
}
from __future__ import annotations
from jaclang.runtimelib.builtin import *
from jaclang import JacMachineInterface as _jl

class Person(_jl.Node):
    name: str
    age: int = 0

class City(_jl.Node):
    name: str

class LivesIn(_jl.Edge):
    years: int = 0

class Friend(_jl.Edge):
    since: int = 2020

class Colleague(_jl.Edge):
    department: str

class GraphBuilder(_jl.Walker):

    @_jl.entry
    def build(self, here: _jl.Root) -> None:
        print('=== 1. Untyped Connect Operators ===')
        alice = Person(name='Alice', age=30)
        bob = Person(name='Bob', age=25)
        charlie = Person(name='Charlie', age=28)
        _jl.connect(left=alice, right=bob)
        print(f'Forward: {alice.name} ++> {bob.name}')
        _jl.connect(left=charlie, right=bob)
        print(f'Backward: {bob.name} <++ {charlie.name} (edge from Charlie to Bob)')
        _jl.connect(left=alice, right=charlie, undir=True)
        print(f'Bidirectional: {alice.name} <++> {charlie.name} (both directions)')
        print('\n=== 2. Typed Connect Operators ===')
        diana = Person(name='Diana', age=32)
        eve = Person(name='Eve', age=27)
        frank = Person(name='Frank', age=29)
        nyc = City(name='New York')
        london = City(name='London')
        _jl.connect(left=diana, right=nyc, edge=LivesIn)
        print(f'Typed forward: {diana.name} +>:LivesIn:+> {nyc.name}')
        _jl.connect(left=london, right=eve, edge=LivesIn)
        print(f'Typed backward: {eve.name} <+:LivesIn:<+ {london.name} (edge from London to Eve)')
        _jl.connect(left=diana, right=eve, edge=Friend, undir=True)
        print(f'Typed bidirectional: {diana.name} <+:Friend:+> {eve.name}')
        print('\n=== 3. Edge Attribute Initialization ===')
        grace = Person(name='Grace', age=26)
        henry = Person(name='Henry', age=31)
        iris = Person(name='Iris', age=24)
        _jl.connect(left=grace, right=henry, edge=Friend(since=2015))
        print(f'Forward with attrs: {grace.name} +>: Friend(since=2015) :+> {henry.name}')
        _jl.connect(left=iris, right=henry, edge=Friend(since=2018))
        print(f'Backward with attrs: {henry.name} <+: Friend(since=2018) :<+ {iris.name}')
        _jl.connect(left=grace, right=iris, edge=Colleague(department='Engineering'), undir=True)
        print(f"Bidirectional with attrs: {grace.name} <+: Colleague(department='Engineering') :+> {iris.name}")
        print('\n=== 4. Chained Connections ===')
        jack = Person(name='Jack', age=35)
        kate = Person(name='Kate', age=29)
        liam = Person(name='Liam', age=30)
        mike = Person(name='Mike', age=33)
        _jl.connect(left=_jl.connect(left=_jl.connect(left=jack, right=kate), right=liam), right=mike)
        print(f'Chain: {jack.name} ++> {kate.name} ++> {liam.name} ++> {mike.name}')
        print('\n=== 5. Inline Node Creation ===')
        nina = Person(name='Nina', age=28)
        _jl.connect(left=nina, right=Person(name='InlineNode1', age=35))
        _jl.connect(left=nina, right=Person(name='InlineNode2', age=40), edge=Friend)
        _jl.connect(left=nina, right=Person(name='InlineNode3', age=45), edge=Friend(since=2010))
        print('Connected to 3 inline-created nodes (untyped, typed, with attrs)')
        print('\n=== 6. Connect to Multiple Targets ===')
        oscar = Person(name='Oscar', age=27)
        paula = Person(name='Paula', age=26)
        quinn = Person(name='Quinn', age=24)
        _jl.connect(left=oscar, right=paula)
        _jl.connect(left=oscar, right=quinn)
        _jl.connect(left=oscar, right=Person(name='Rita', age=30), edge=Friend)
        print(f'Connected {oscar.name} to 3 different targets')
        print('\n=== 7. Disconnect Operator ===')
        print('Disconnect syntax: node del [-->] target (deletes edges from node to target)')
        print('\n=== 8. Connect in Expressions ===')
        steve = Person(name='Steve', age=45)
        tina = Person(name='Tina', age=42)
        _jl.connect(left=steve, right=tina)
        print(f'Connect used in expression: {steve.name} ++> {tina.name}')
        print('\n✓ All connect expression features demonstrated!')
        _jl.disengage(self)
        return

class EdgeTraverser(_jl.Walker):

    @_jl.entry
    def traverse(self, here: _jl.Root) -> None:
        print('\n=== Edge Traversal with Visit ===')
        a = Person(name='A', age=25)
        b = Person(name='B', age=30)
        c = Person(name='C', age=35)
        _jl.connect(left=_jl.root(), right=a)
        _jl.connect(left=a, right=b, edge=Friend(since=2010))
        _jl.connect(left=a, right=c, edge=Colleague(department='Sales'))
        _jl.connect(left=b, right=c, edge=Friend(since=2015))
        print('Graph: root->A, A-Friend->B, A-Colleague->C, B-Friend->C')
        print('Visiting all outgoing edges from root:')
        _jl.visit(self, _jl.refs(_jl.Path(here)._out().visit()))

    @_jl.entry
    def traverse(self, here: Person) -> None:
        print(f'  Visited: {here.name}, age={here.age}')
_jl.spawn(_jl.root(), GraphBuilder())
_jl.spawn(_jl.root(), EdgeTraverser())
Jac Grammar Snippet
connect: (connect (connect_op | disconnect_op))? atomic_pipe

Description

Connect Expressions (OSP)

Connect expressions are specialized operators for creating edges between nodes in graph structures. These operators are fundamental to Jac's Object-Spatial Programming (OSP), providing first-class syntax for spatial relationships.

Connect Operator Types

Direction Untyped Typed With Attributes
Forward (A → B) ++> +>:Type:+> +>: Type(...) :+>
Backward (A ← B) <++ <+:Type:<+ <+: Type(...) :<+
Bidirectional (A ↔ B) <++> <+:Type:+> <+: Type(...) :+>

Node and Edge Definitions (Lines 4-23)

The example defines nodes and edges for the graph:

Lines 4-7: Person node with name and age attributes Lines 9-11: City node with name attribute Lines 13-15: LivesIn edge with years attribute Lines 17-19: Friend edge with since attribute Lines 21-23: Colleague edge with department attribute

Untyped Connect Operators (Lines 28-41)

These create generic edges without specifying an edge type:

Forward connection (Line 34): - Arrow points right: alice → bob - Line 35 prints confirmation

Backward connection (Line 37): - Arrow points left: charlie → bob (not bob → charlie!) - Line 38 notes the actual direction

Bidirectional connection (Line 40): - Creates alice → charlie AND charlie → alice - Line 41 confirms both directions created

Direction Flow Diagram

graph LR
    A[alice] -->|++>| B[bob]
    C[charlie] -->|<++| B
    A <-->|<++>| C

    style A fill:#1565c0,stroke:#fff,color:#fff
    style B fill:#1565c0,stroke:#fff,color:#fff
    style C fill:#1565c0,stroke:#fff,color:#fff

Typed Connect Operators (Lines 43-58)

These specify the edge archetype during connection:

Forward typed (Line 51): - Creates LivesIn edge from diana to nyc - Edge type is explicitly LivesIn - Line 52 shows the syntax

Backward typed (Line 54): - Creates LivesIn edge from london to eve (not eve to london!) - Line 55 clarifies the actual direction

Bidirectional typed (Line 57): - Creates Friend edges in both directions - Both diana → eve and eve → diana are Friend type

Edge Attribute Initialization (Lines 60-73)

Initialize edge attributes during connection:

Forward with attributes (Line 66): - Creates Friend edge from grace to henry - Sets since attribute to 2015 - Line 67 shows the full syntax

Backward with attributes (Line 69): - Creates Friend edge from iris to henry - Edge has since=2018

Bidirectional with attributes (Line 72): - Creates Colleague edges in both directions - Both edges have department="Engineering"

Chained Connections (Lines 75-83)

Connect operators can be chained left-to-right:

Line 82: jack ++> kate ++> liam ++> mike; - Creates path: jack → kate → liam → mike - Evaluates left-to-right - Line 83 shows the resulting chain

Chaining Visualization

graph LR
    J[jack] --> K[kate]
    K --> L[liam]
    L --> M[mike]

Inline Node Creation (Lines 85-92)

Create nodes directly in connect expressions:

Line 89: nina ++> Person(name="InlineNode1", age=35); - Creates a new Person node inline - Immediately connects nina to it - No need for intermediate variable

Line 90: nina +>:Friend:+> Person(name="InlineNode2", age=40); - Inline node creation with typed edge

Line 91: nina +>: Friend(since=2010) :+> Person(name="InlineNode3", age=45); - Combines inline creation with edge attributes

Multiple Target Connections (Lines 94-103)

One node can connect to multiple targets:

Lines 100-102: - Oscar connects to three different nodes - Creates a hub/star pattern - Line 103 confirms all connections

Hub Pattern Diagram

graph TD
    O[oscar] --> P[paula]
    O --> Q[quinn]
    O --> R[Rita]

Disconnect Operator (Lines 105-108)

Line 107: node del [-->] target - Deletes edges from node to target - Uses del keyword with edge reference syntax - Can specify edge type: node del [->:Type:->] target

Connect in Expressions (Lines 110-116)

Connect expressions return values and integrate with other expressions:

Lines 115-116: - Connect can be part of larger expressions - Returns a value that can be used in subsequent code

Edge Traversal Integration (Lines 123-146)

The example demonstrates how created edges are traversed:

Building the graph (Lines 133-136):

Traversing edges (Line 140): - Visits all outgoing edges from current node - Uses edge reference syntax (different from connect operators)

Traversal Flow

graph TD
    Root --> A[Person A]
    A -->|Friend since=2010| B[Person B]
    A -->|Colleague Sales| C[Person C]
    B -->|Friend since=2015| C

    W[Walker] -.->|visit| A
    W -.->|visit| B
    W -.->|visit| C

Connect vs Edge References

Aspect Connect Operators Edge References
Purpose CREATE edges TRAVERSE edges
Examples ++>, +>:Type:+> [-->], [->:Type:->]
Usage Graph construction Visit statements, queries
Action Imperative (make connection) Declarative (find connections)
Context Building graph structure Navigating graph structure

Common Graph Patterns

Linear chain (line 82):

Star/hub (lines 100-102):

Bidirectional network:

Heterogeneous typed graph (lines 134-136):

Operator Directionality Guide

Understanding arrow direction is critical:

Code Actual Edge Direction Memory Aid
a ++> b a → b Arrow points to target
a <++ b b → a Arrow points away, so b → a
a <++> b a ↔ b Arrows both ways = both edges

Best Practices

  1. Choose typed edges for clarity: Use +>:Type:+> when edge semantics matter
  2. Initialize attributes during connect: Cleaner than setting attributes after
  3. Use backward operators intentionally: <++ can improve code readability
  4. Chain for linear structures: Paths and sequences benefit from chaining
  5. Inline creation for temporary nodes: Reduces variable clutter
  6. Document edge semantics: Comment complex graph structures

Integration with OSP

Connect expressions work seamlessly with walkers:

Building during traversal:

Conditional connections: