Skip to content

Edge references (OSP)#

Code Example

Runnable Example in Jac and JacLib

"""Edge references (OSP): Edge reference expressions for graph queries."""

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

edge Friend {
    has since: int = 2020;
}

edge Colleague {
    has years: int = 0;
}

# ===== Walker Demonstrating Edge References =====
walker EdgeRefWalker {
    can demonstrate with `root entry {
        print("=== 1. Basic Edge References ===\n");

        # Build simple graph
        alice = Person(name="Alice");
        bob = Person(name="Bob");
        charlie = Person(name="Charlie");

        root ++> alice;
        alice +>: Friend(since=2015) :+> bob;
        alice +>: Colleague(years=3) :+> charlie;

        # Basic outgoing edge reference [-->]
        print("From root:");
        out_from_root = [-->];
        print(f"  [-->] found {len(out_from_root)} outgoing nodes");
        for n in out_from_root {
            print(f"    - {n.name}");
        }

        # Move to Alice to demonstrate more
        visit [-->];
    }

    can show_refs with Person entry {
        if here.name == "Alice" {
            print(f"\n=== 2. Edge References from {here.name} ===\n");

            # Outgoing [-->]
            outgoing = [-->];
            print(f"Outgoing [-->]: {len(outgoing)} nodes");
            for n in outgoing {
                print(f"  - {n.name}");
            }

            # Incoming [<--]
            incoming = [<--];
            print(f"\nIncoming [<--]: {len(incoming)} nodes");

            # Bidirectional [<-->]
            both = [<-->];
            print(f"\nBidirectional [<-->]: {len(both)} nodes");

            print("\n=== 3. Typed Edge References ===\n");

            # Typed outgoing [->:Type:->]
            friends = [->:Friend:->];
            print(f"Friend edges [->:Friend:->]: {len(friends)} nodes");
            for n in friends {
                print(f"  - {n.name}");
            }

            colleagues = [->:Colleague:->];
            print(f"Colleague edges [->:Colleague:->]: {len(colleagues)} nodes");
            for n in colleagues {
                print(f"  - {n.name}");
            }

            print("\n=== 4. Filtered Edge References ===\n");

            # Filter on edge attributes
            old_friends = [->:Friend:since < 2018:->];
            print(f"Friends since before 2018: {len(old_friends)} nodes");

            experienced_colleagues = [->:Colleague:years > 2:->];
            print(f"Colleagues with years > 2: {len(experienced_colleagues)} nodes");

            # Multiple filter conditions
            specific = [->:Colleague:years >= 1, years <= 5:->];
            print(f"Colleagues with 1-5 years: {len(specific)} nodes");

            print("\n=== 5. Edge and Node Keywords ===\n");

            # Get edge objects [edge -->]
            edge_objs = [edge -->];
            print(f"[edge -->]: Retrieved {len(edge_objs)} edge objects");

            # Get node objects [node -->] (explicit)
            node_objs = [node -->];
            print(f"[node -->]: Retrieved {len(node_objs)} node objects");

            print("\n=== 6. Chained Edge References ===\n");

            # Chained edge references - multiple hops in one expression
            # Syntax: [node ->:Type1:-> ->:Type2:->]
            # This traverses Type1 edges, then Type2 edges from those nodes

            # Build deeper graph for chaining demo
            david = Person(name="David");
            # Get bob from outgoing Friend edges
            bob_list = [here ->:Friend:->];
            if bob_list {
                bob_list[0] +>: Friend(since=2018) :+> david;
            }

            # Two-hop chain: Friend -> Friend
            two_hop = [here ->:Friend:-> ->:Friend:->];
            print(f"[here ->:Friend:-> ->:Friend:->]: {len(two_hop)} nodes (2 hops via Friend)");

            # Mixed type chain: Friend -> Colleague
            # This goes through Friend edges, then Colleague edges from those nodes
            mixed = [here ->:Friend:-> ->:Colleague:->];
            print(f"[here ->:Friend:-> ->:Colleague:->]: {len(mixed)} nodes (Friend then Colleague)");

            # Can chain many hops
            print("Can chain multiple: [node ->:T1:-> ->:T2:-> ->:T3:->]");

            print("\n=== 7. Edge References in Different Contexts ===\n");

            # In assignment
            targets = [-->];
            print(f"Assignment: targets = [-->] → {len(targets)} nodes");

            # In if condition
            if [-->] {
                print("Conditional: if [-->] → edges exist!");
            }

            # In for loop
            print("For loop:");
            for person in [-->] {
                print(f"  Iterating: {person.name}");
            }

            # In visit statement (most common)
            print("\nVisit statement: visit [->:Friend:->]");
            # visit [->:Friend:->];  # Would visit friends
        }

        disengage;
    }
}

# ===== Summary Walker =====
walker Summary {
    can show with `root entry {
        print("\n" + "="*50);
        print("EDGE REFERENCE SYNTAX SUMMARY");
        print("="*50);

        print("\n** Basic Forms **");
        print("  [-->]           All outgoing edges");
        print("  [<--]           All incoming edges");
        print("  [<-->]          Bidirectional (both ways)");

        print("\n** Typed Forms **");
        print("  [->:Type:->]    Outgoing of specific type");
        print("  [<-:Type:<-]    Incoming of specific type");
        print("  [<-:Type:->]    Bidirectional of specific type");

        print("\n** Filtered Forms **");
        print("  [->:Type:attr > val:->]       Filter on edge attribute");
        print("  [->:Type:a > x, b < y:->]     Multiple conditions");

        print("\n** Special Forms **");
        print("  [edge -->]                    Get edge objects");
        print("  [node -->]                    Get node objects (explicit)");
        print("  [node ->:T1:-> ->:T2:->]      Chained (multi-hop)");

        print("\n** Common Usage **");
        print("  visit [-->];                  Visit all outgoing");
        print("  for n in [-->] { ... }        Iterate over nodes");
        print("  if [-->] { ... }              Check if edges exist");
        print("  targets = [->:Type:->];       Store in variable");

        print("\n" + "="*50);
    }
}

# ===== Execution =====
with entry {
    print("=== Object Spatial References Demo ===\n");
    root spawn EdgeRefWalker();
    root spawn Summary();
    print("\n✓ Edge reference variations demonstrated!");
}
"""Edge references (OSP): Edge reference expressions for graph queries."""

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

edge Friend {
    has since: int = 2020;
}

edge Colleague {
    has years: int = 0;
}

# ===== Walker Demonstrating Edge References =====
walker EdgeRefWalker {
    can demonstrate with `root entry {
        print("=== 1. Basic Edge References ===\n");

        # Build simple graph
        alice = Person(name="Alice");
        bob = Person(name="Bob");
        charlie = Person(name="Charlie");

        root ++> alice;
        alice +>: Friend(since=2015) :+> bob;
        alice +>: Colleague(years=3) :+> charlie;

        # Basic outgoing edge reference [-->]
        print("From root:");
        out_from_root = [-->];
        print(f"  [-->] found {len(out_from_root)} outgoing nodes");
        for n in out_from_root {
            print(f"    - {n.name}");
        }

        # Move to Alice to demonstrate more
        visit [-->];
    }

    can show_refs with Person entry {
        if here.name == "Alice" {
            print(f"\n=== 2. Edge References from {here.name} ===\n");

            # Outgoing [-->]
            outgoing = [-->];
            print(f"Outgoing [-->]: {len(outgoing)} nodes");
            for n in outgoing {
                print(f"  - {n.name}");
            }

            # Incoming [<--]
            incoming = [<--];
            print(f"\nIncoming [<--]: {len(incoming)} nodes");

            # Bidirectional [<-->]
            both = [<-->];
            print(f"\nBidirectional [<-->]: {len(both)} nodes");

            print("\n=== 3. Typed Edge References ===\n");

            # Typed outgoing [->:Type:->]
            friends = [->:Friend:->];
            print(f"Friend edges [->:Friend:->]: {len(friends)} nodes");
            for n in friends {
                print(f"  - {n.name}");
            }

            colleagues = [->:Colleague:->];
            print(f"Colleague edges [->:Colleague:->]: {len(colleagues)} nodes");
            for n in colleagues {
                print(f"  - {n.name}");
            }

            print("\n=== 4. Filtered Edge References ===\n");

            # Filter on edge attributes
            old_friends = [->:Friend:since < 2018:->];
            print(f"Friends since before 2018: {len(old_friends)} nodes");

            experienced_colleagues = [->:Colleague:years > 2:->];
            print(f"Colleagues with years > 2: {len(experienced_colleagues)} nodes");

            # Multiple filter conditions
            specific = [->:Colleague:years >= 1, years <= 5:->];
            print(f"Colleagues with 1-5 years: {len(specific)} nodes");

            print("\n=== 5. Edge and Node Keywords ===\n");

            # Get edge objects [edge -->]
            edge_objs = [edge -->];
            print(f"[edge -->]: Retrieved {len(edge_objs)} edge objects");

            # Get node objects [node -->] (explicit)
            node_objs = [node -->];
            print(f"[node -->]: Retrieved {len(node_objs)} node objects");

            print("\n=== 6. Chained Edge References ===\n");

            # Chained edge references - multiple hops in one expression
            # Syntax: [node ->:Type1:-> ->:Type2:->]
            # This traverses Type1 edges, then Type2 edges from those nodes

            # Build deeper graph for chaining demo
            david = Person(name="David");
            # Get bob from outgoing Friend edges
            bob_list = [here ->:Friend:->];
            if bob_list {
                bob_list[0] +>: Friend(since=2018) :+> david;
            }

            # Two-hop chain: Friend -> Friend
            two_hop = [here ->:Friend:-> ->:Friend:->];
            print(f"[here ->:Friend:-> ->:Friend:->]: {len(two_hop)} nodes (2 hops via Friend)");

            # Mixed type chain: Friend -> Colleague
            # This goes through Friend edges, then Colleague edges from those nodes
            mixed = [here ->:Friend:-> ->:Colleague:->];
            print(f"[here ->:Friend:-> ->:Colleague:->]: {len(mixed)} nodes (Friend then Colleague)");

            # Can chain many hops
            print("Can chain multiple: [node ->:T1:-> ->:T2:-> ->:T3:->]");

            print("\n=== 7. Edge References in Different Contexts ===\n");

            # In assignment
            targets = [-->];
            print(f"Assignment: targets = [-->] → {len(targets)} nodes");

            # In if condition
            if [-->] {
                print("Conditional: if [-->] → edges exist!");
            }

            # In for loop
            print("For loop:");
            for person in [-->] {
                print(f"  Iterating: {person.name}");
            }

            # In visit statement (most common)
            print("\nVisit statement: visit [->:Friend:->]");
            # visit [->:Friend:->];  # Would visit friends
        }

        disengage;
    }
}

# ===== Summary Walker =====
walker Summary {
    can show with `root entry {
        print("\n" + "="*50);
        print("EDGE REFERENCE SYNTAX SUMMARY");
        print("="*50);

        print("\n** Basic Forms **");
        print("  [-->]           All outgoing edges");
        print("  [<--]           All incoming edges");
        print("  [<-->]          Bidirectional (both ways)");

        print("\n** Typed Forms **");
        print("  [->:Type:->]    Outgoing of specific type");
        print("  [<-:Type:<-]    Incoming of specific type");
        print("  [<-:Type:->]    Bidirectional of specific type");

        print("\n** Filtered Forms **");
        print("  [->:Type:attr > val:->]       Filter on edge attribute");
        print("  [->:Type:a > x, b < y:->]     Multiple conditions");

        print("\n** Special Forms **");
        print("  [edge -->]                    Get edge objects");
        print("  [node -->]                    Get node objects (explicit)");
        print("  [node ->:T1:-> ->:T2:->]      Chained (multi-hop)");

        print("\n** Common Usage **");
        print("  visit [-->];                  Visit all outgoing");
        print("  for n in [-->] { ... }        Iterate over nodes");
        print("  if [-->] { ... }              Check if edges exist");
        print("  targets = [->:Type:->];       Store in variable");

        print("\n" + "="*50);
    }
}

# ===== Execution =====
with entry {
    print("=== Object Spatial References Demo ===\n");
    root spawn EdgeRefWalker();
    root spawn Summary();
    print("\n✓ Edge reference variations demonstrated!");
}
from __future__ import annotations
from jaclang.runtimelib.builtin import *
from jaclang import JacMachineInterface as _jl

class Person(_jl.Node):
    name: str

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

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

class EdgeRefWalker(_jl.Walker):

    @_jl.entry
    def demonstrate(self, here: _jl.Root) -> None:
        print('=== 1. Basic Edge References ===\n')
        alice = Person(name='Alice')
        bob = Person(name='Bob')
        charlie = Person(name='Charlie')
        _jl.connect(left=_jl.root(), right=alice)
        _jl.connect(left=alice, right=bob, edge=Friend(since=2015))
        _jl.connect(left=alice, right=charlie, edge=Colleague(years=3))
        print('From root:')
        out_from_root = _jl.refs(_jl.Path(here)._out())
        print(f'  [-->] found {len(out_from_root)} outgoing nodes')
        for n in out_from_root:
            print(f'    - {n.name}')
        _jl.visit(self, _jl.refs(_jl.Path(here)._out().visit()))

    @_jl.entry
    def show_refs(self, here: Person) -> None:
        if here.name == 'Alice':
            print(f'\\n=== 2. Edge References from {here.name} ===\\n')
            outgoing = _jl.refs(_jl.Path(here)._out())
            print(f'Outgoing [-->]: {len(outgoing)} nodes')
            for n in outgoing:
                print(f'  - {n.name}')
            incoming = _jl.refs(_jl.Path(here)._in())
            print(f'\\nIncoming [<--]: {len(incoming)} nodes')
            both = _jl.refs(_jl.Path(here)._any())
            print(f'\\nBidirectional [<-->]: {len(both)} nodes')
            print('\n=== 3. Typed Edge References ===\n')
            friends = _jl.refs(_jl.Path(here)._out(edge=lambda i: isinstance(i, Friend)))
            print(f'Friend edges [->:Friend:->]: {len(friends)} nodes')
            for n in friends:
                print(f'  - {n.name}')
            colleagues = _jl.refs(_jl.Path(here)._out(edge=lambda i: isinstance(i, Colleague)))
            print(f'Colleague edges [->:Colleague:->]: {len(colleagues)} nodes')
            for n in colleagues:
                print(f'  - {n.name}')
            print('\n=== 4. Filtered Edge References ===\n')
            old_friends = _jl.refs(_jl.Path(here)._out(edge=lambda i: isinstance(i, Friend) and i.since < 2018))
            print(f'Friends since before 2018: {len(old_friends)} nodes')
            experienced_colleagues = _jl.refs(_jl.Path(here)._out(edge=lambda i: isinstance(i, Colleague) and i.years > 2))
            print(f'Colleagues with years > 2: {len(experienced_colleagues)} nodes')
            specific = _jl.refs(_jl.Path(here)._out(edge=lambda i: isinstance(i, Colleague) and i.years >= 1 and (i.years <= 5)))
            print(f'Colleagues with 1-5 years: {len(specific)} nodes')
            print('\n=== 5. Edge and Node Keywords ===\n')
            edge_objs = _jl.refs(_jl.Path(here)._out().edge())
            print(f'[edge -->]: Retrieved {len(edge_objs)} edge objects')
            node_objs = _jl.refs(_jl.Path(here)._out())
            print(f'[node -->]: Retrieved {len(node_objs)} node objects')
            print('\n=== 6. Chained Edge References ===\n')
            david = Person(name='David')
            bob_list = _jl.refs(_jl.Path(here)._out(edge=lambda i: isinstance(i, Friend)))
            if bob_list:
                _jl.connect(left=bob_list[0], right=david, edge=Friend(since=2018))
            two_hop = _jl.refs(_jl.Path(here)._out(edge=lambda i: isinstance(i, Friend))._out(edge=lambda i: isinstance(i, Friend)))
            print(f'[here ->:Friend:-> ->:Friend:->]: {len(two_hop)} nodes (2 hops via Friend)')
            mixed = _jl.refs(_jl.Path(here)._out(edge=lambda i: isinstance(i, Friend))._out(edge=lambda i: isinstance(i, Colleague)))
            print(f'[here ->:Friend:-> ->:Colleague:->]: {len(mixed)} nodes (Friend then Colleague)')
            print('Can chain multiple: [node ->:T1:-> ->:T2:-> ->:T3:->]')
            print('\n=== 7. Edge References in Different Contexts ===\n')
            targets = _jl.refs(_jl.Path(here)._out())
            print(f'Assignment: targets = [-->] → {len(targets)} nodes')
            if _jl.refs(_jl.Path(here)._out()):
                print('Conditional: if [-->] → edges exist!')
            print('For loop:')
            for person in _jl.refs(_jl.Path(here)._out()):
                print(f'  Iterating: {person.name}')
            print('\nVisit statement: visit [->:Friend:->]')
        _jl.disengage(self)
        return

class Summary(_jl.Walker):

    @_jl.entry
    def show(self, here: _jl.Root) -> None:
        print('\n' + '=' * 50)
        print('EDGE REFERENCE SYNTAX SUMMARY')
        print('=' * 50)
        print('\n** Basic Forms **')
        print('  [-->]           All outgoing edges')
        print('  [<--]           All incoming edges')
        print('  [<-->]          Bidirectional (both ways)')
        print('\n** Typed Forms **')
        print('  [->:Type:->]    Outgoing of specific type')
        print('  [<-:Type:<-]    Incoming of specific type')
        print('  [<-:Type:->]    Bidirectional of specific type')
        print('\n** Filtered Forms **')
        print('  [->:Type:attr > val:->]       Filter on edge attribute')
        print('  [->:Type:a > x, b < y:->]     Multiple conditions')
        print('\n** Special Forms **')
        print('  [edge -->]                    Get edge objects')
        print('  [node -->]                    Get node objects (explicit)')
        print('  [node ->:T1:-> ->:T2:->]      Chained (multi-hop)')
        print('\n** Common Usage **')
        print('  visit [-->];                  Visit all outgoing')
        print('  for n in [-->] { ... }        Iterate over nodes')
        print('  if [-->] { ... }              Check if edges exist')
        print('  targets = [->:Type:->];       Store in variable')
        print('\n' + '=' * 50)
print('=== Object Spatial References Demo ===\n')
_jl.spawn(_jl.root(), EdgeRefWalker())
_jl.spawn(_jl.root(), Summary())
print('\n✓ Edge reference variations demonstrated!')
Jac Grammar Snippet
edge_ref_chain: LSQUARE KW_ASYNC? (KW_NODE| KW_EDGE)? expression? (edge_op_ref (filter_compr | expression)?)+ RSQUARE
edge_op_ref: edge_any | edge_from | edge_to
edge_to: ARROW_R | ARROW_R_P1 typed_filter_compare_list ARROW_R_P2
edge_from: ARROW_L | ARROW_L_P1 typed_filter_compare_list ARROW_L_P2
edge_any: ARROW_BI | ARROW_L_P1 typed_filter_compare_list ARROW_R_P2
connect_op: connect_from | connect_to | connect_any
disconnect_op: KW_DELETE edge_op_ref
connect_to: CARROW_R | CARROW_R_P1 expression (COLON kw_expr_list)? CARROW_R_P2
connect_from: CARROW_L | CARROW_L_P1 expression (COLON kw_expr_list)? CARROW_L_P2
connect_any: CARROW_BI | CARROW_L_P1 expression (COLON kw_expr_list)? CARROW_R_P2

Description

Edge reference expressions are the fundamental mechanism for querying and traversing graph structures in Jac. Unlike connect operators that CREATE edges, edge references QUERY existing edges to retrieve connected nodes or edge objects.

What are Edge References?

Edge references let you ask questions like "which nodes are connected to this one?" or "what edges of type Friend exist?". They're enclosed in square brackets and return lists of nodes or edges.

Think of edge references as the "SELECT" statement of graph databases - they let you query the graph structure without modifying it.

Basic Directional Forms

Lines 32-59 demonstrate the three fundamental directions:

Syntax Direction Returns Example Line
[-->] Outgoing Nodes connected via outgoing edges 32
[<--] Incoming Nodes with edges pointing to here 54
[<-->] Bidirectional Nodes connected in either direction 58

Line 32 shows out_from_root = [-->], which retrieves all nodes connected via outgoing edges from root. Since line 26 connected Alice to root, the result includes Alice.

Lines 47-59 show all three directions from Alice's perspective: - Outgoing (line 47): Bob and Charlie (Alice connects to them) - Incoming (line 54): Root (root connects to Alice) - Bidirectional (line 58): All three (root, Bob, Charlie)

graph LR
    R[Root] -->|outgoing from Root| A[Alice]
    A -->|outgoing from Alice| B[Bob]
    A -->|outgoing from Alice| C[Charlie]

    style A fill:#2e7d32,stroke:#fff,color:#fff

    Note1[From Root: <br/> -->  = Alice]
    Note2[From Alice: <br/> -->  = Bob, Charlie <br/> <--  = Root <br/> <-->  = Root, Bob, Charlie]

Typed Edge References

Lines 64-74 show filtering by edge type. The syntax [->:Type:->] traverses only edges of the specified type. Line 27 creates a Friend edge to Bob, and line 28 creates a Colleague edge to Charlie. Line 64 finds only Bob (Friend connections), while line 70 finds only Charlie (Colleague connections).

Pattern Matches Example
[->:Friend:->] Outgoing Friend edges Line 64
[<-:Friend:<-] Incoming Friend edges -
[<-:Friend:->] Friend edges in either direction -

The type name between colons filters which edges to traverse.

Filtered Edge References

Lines 79-87 demonstrate filtering on edge attributes. The syntax [->:Type:condition:->] filters edges based on their attributes: - Line 79: Only Friend edges where the since attribute is less than 2018 - Line 82: Only Colleague edges where years attribute is greater than 2 - Line 86: Multiple conditions combined with AND logic (years between 1 and 5)

The filters apply to EDGE attributes, not node attributes. Line 27 creates Friend(since=2015), so the filter on line 79 matches because 2015 < 2018.

Edge vs Node Retrieval

Lines 92-97 show retrieving edge objects instead of nodes:

Keyword Returns Use When
[edge -->] Edge objects Need to access edge attributes
[node -->] Node objects Need to access node attributes (default)
[-->] Node objects Default behavior, same as [node -->]

Line 92 returns edge objects that have attributes like since (Friend edges) or years (Colleague edges). This is useful when you need to examine or modify edge properties.

Chained Edge References for Multi-Hop Traversal

Lines 114-120 demonstrate chaining to traverse multiple hops. Chaining syntax: [start ->:Type1:-> ->:Type2:->]

Line 114 performs a two-hop traversal: 1. Start at here (Alice) 2. Follow Friend edges to reach Bob 3. From Bob, follow Friend edges to reach David 4. Returns David (2 hops away)

graph LR
    A[Alice] -->|Friend| B[Bob]
    B -->|Friend| D[David]

    style A fill:#2e7d32,stroke:#fff,color:#fff
    style D fill:#b71c1c,stroke:#fff,color:#fff

    Note[here ->:Friend:-> ->:Friend:-> <br/> starts at Alice, returns David]

Line 119 shows mixed-type chaining - Friend edges then Colleague edges. You can chain as many hops as needed: [node ->:T1:-> ->:T2:-> ->:T3:->].

Edge References in Different Contexts

Lines 128-144 show edge references used in various ways:

Context Syntax Purpose Example Line
Assignment targets = [-->] Store results 128
Conditional if [-->] { ... } Check if edges exist 132
For loop for n in [-->] { ... } Iterate nodes 138
Visit statement visit [->:Friend:->] Walker traversal 143

Line 132 shows using an edge reference as a boolean condition - non-empty lists are truthy, so this checks "do any outgoing edges exist?".

Lines 138-140 show iteration, which is very common for processing all neighbors.

Evaluation Context

Edge references are evaluated relative to a spatial context:

Implicit context (inside walker abilities): - Line 47: outgoing = [-->] evaluates from here (current node) - Most common usage in walkers

Explicit context: - Lines 108, 114, 119: [here ->:Type:->] explicitly specifies starting node - Can use any node variable: [root -->], [alice -->], [some_node -->]

When no starting node is specified in walker abilities, here is the implicit starting point.

Edge References vs Connect Operators

Understanding the difference is crucial:

Feature Edge Reference Connect Operator
Purpose Query existing edges Create new edges
Syntax [-->] ++>
Returns List of nodes/edges Target node
Modifies graph No Yes
Example line 32, 47, 64 26, 27, 28

Lines 26-28 use connect operators (++>, +>:Type:+>) to CREATE the graph structure. Lines 32-144 use edge references to QUERY that structure.

The workflow: First connect to build the graph, then reference to traverse it.

Common Edge Reference Patterns

Pattern 1: Neighbor Query (line 47)

Pattern 2: Typed Relationship Query (line 64)

Pattern 3: Filtered Relationship Query (line 79)

Pattern 4: Multi-Hop Query (line 114)

Pattern 5: Existence Check (line 132)

Pattern 6: Edge Inspection (line 92)

Complete Syntax Reference

The example's summary walker (lines 152-185) provides a comprehensive syntax reference:

Basic forms: - [-->] - All outgoing edges - [<--] - All incoming edges - [<-->] - Bidirectional (both ways)

Typed forms: - [->:Type:->] - Outgoing of specific type - [<-:Type:<-] - Incoming of specific type - [<-:Type:->] - Bidirectional of specific type

Filtered forms: - [->:Type:attr > val:->] - Filter on edge attribute - [->:Type:a > x, b < y:->] - Multiple conditions (AND)

Special forms: - [edge -->] - Get edge objects - [node -->] - Get node objects (explicit) - [node ->:T1:-> ->:T2:->] - Chained (multi-hop)

Common Usage Patterns

In visit statements (most common):

In for loops:

In conditionals:

In assignments:

Understanding Return Values

All edge references return lists (collections):

  • [-->] returns a list of nodes (or empty list [] if no matches)
  • [edge -->] returns a list of edge objects
  • Can check length: len([-->])
  • Can use in boolean context: if [-->] (empty = false, non-empty = true)

Common Mistakes to Avoid

  1. Filtering on wrong attributes:
  2. ✗ Wrong: [->:Friend:->](?since < 2020) - this is comprehension syntax
  3. ✓ Right: [->:Friend:since < 2020:->] - edge attribute filter

  4. Incorrect chaining:

  5. ✗ Wrong: [-->][-->] - can't concatenate brackets
  6. ✓ Right: [here --> -->] or [here ->:T1:-> ->:T2:->] - chain within brackets

  7. Context confusion:

  8. Edge reference [-->] in walker evaluates from here, not spawn point
  9. Use explicit node if needed: [root -->]

  10. Confusing reference and connect:

  11. [-->] queries edges (read-only)
  12. ++> creates edges (write)

Practical Applications

Edge references enable key graph operations:

  1. Graph queries: "Find all nodes of type X connected via relationship Y"
  2. Path exploration: "What's 3 hops away via Friend edges?"
  3. Relationship inspection: "What edges exist from this node?"
  4. Filtered traversal: "Only follow edges where attribute > threshold"
  5. Reverse lookup: "Who points to me?" (incoming edges)
  6. Pattern matching: "Find nodes matching this graph pattern"

Edge references are the query language for Jac's graph model, providing declarative, concise syntax for navigating spatial structures. Combined with visit statements and walkers, they form the foundation of Object-Spatial Programming's data-to-computation paradigm.