Skip to content

Filter and assign comprehensions#

Code Example

Runnable Example in Jac and JacLib

"""Filter and assign comprehensions: Filter and assign comprehension syntax."""

import random;

# ===== Object Definitions =====
obj TestObj {
    has x: int = 0,
        y: int = 0,
        z: int = 0;
}

obj Person {
    has name: str,
        age: int = 0,
        score: int = 0;
}

# ===== Node and Edge Definitions for Spatial Comprehensions =====
node Employee {
    has name: str,
        salary: int = 0,
        department: str = "Unknown";
}

edge ReportsTo {
    has years: int = 0;
}

edge Collaborates {
    has project: str = "None";
}

# ===== Walker for Demonstrating Edge Comprehensions =====
walker ComprehensionDemo {
    can demo_basic with `root entry {
        print("=== 1. Basic Filter Comprehension ===");
        # Create objects with varying attributes
        random.seed(42);
        items = [];
        for i=0 to i<10 by i+=1 {
            items.append(TestObj(x=random.randint(0, 20), y=random.randint(0, 20), z=i));
        }

        # Filter comprehension: collection(?condition1, condition2, ...)
        # Filters objects where ALL conditions are true
        filtered = items(?x >= 10, y < 15);
        print(f"Filtered {len(items)} items to {len(filtered)} where x>=10 and y<15");

        print("\n=== 2. Filter with Single Condition ===");
        high_x = items(?x > 15);
        print(f"Items with x > 15: {len(high_x)}");

        print("\n=== 3. Filter with Multiple Conditions ===");
        complex_filter = items(?x >= 5, x <= 15, y > 10);
        print(f"Items with 5 <= x <= 15 and y > 10: {len(complex_filter)}");

        print("\n=== 4. Basic Assign Comprehension ===");
        # Create fresh objects
        objs = [TestObj(x=i) for i in range(3)];
        print(f"Before assign: x values = {[o.x for o in objs]}");

        # Assign comprehension: collection(=attr1=val1, attr2=val2, ...)
        # Sets attributes on ALL objects in collection
        objs(=y=100, z=200);
        print(f"After assign(=y=100, z=200): y values = {[o.y for o in objs]}");

        print("\n=== 5. Chained Filter and Assign ===");
        # Filter THEN assign
        people = [
            Person(name="Alice", age=25, score=80),
            Person(name="Bob", age=30, score=90),
            Person(name="Charlie", age=35, score=70)
        ];

        # Filter people age >= 30, then boost their scores
        people(?age >= 30)(=score=95);
        print("People after filter(age>=30) + assign(score=95):");
        for p in people {
            print(f"  {p.name}: age={p.age}, score={p.score}");
        }

        print("\n=== 6. Multiple Chained Filters ===");
        data = [TestObj(x=i, y=i*2, z=i*3) for i in range(10)];
        # Apply multiple filters in sequence
        result = data(?x > 2)(?y < 15)(?z >= 6);
        print(f"Triple filtered from {len(data)} to {len(result)} items");

        # Build graph for spatial comprehensions
        print("\n=== Building Organization Graph ===");
        mgr = Employee(name="Manager", salary=100000, department="Engineering");
        dev1 = Employee(name="Dev1", salary=80000, department="Engineering");
        dev2 = Employee(name="Dev2", salary=75000, department="Engineering");
        dev3 = Employee(name="Dev3", salary=70000, department="Sales");

        root ++> mgr;
        mgr +>: ReportsTo(years=5) :+> dev1;
        mgr +>: ReportsTo(years=2) :+> dev2;
        mgr +>: ReportsTo(years=1) :+> dev3;
        dev1 <+: Collaborates(project="ProjectX") :+> dev2;

        print("Graph built: Manager -> 3 Devs");

        # Visit employees to demonstrate edge comprehensions
        visit [-->];
    }

    can demo_edge_filters with Employee entry {
        print(f"\n=== At {here.name} ===");

        print("=== 7. Filter Comprehension on Edge Results ===");
        # Edge traversal returns nodes; filter on NODE attributes
        # Syntax: [edge_expr](?node_attribute_filter)

        # Get all direct reports and filter by salary
        all_reports = [-->];
        high_paid = all_reports(?salary > 75000);
        print(f"Direct reports: {len(all_reports)}, high paid (>75k): {len(high_paid)}");

        print("\n=== 8. Typed Edge with Node Filter ===");
        # Traverse specific edge type, filter resulting nodes
        reports_via_edge = [->:ReportsTo:->];
        engineering = reports_via_edge(?department == "Engineering");
        print(f"ReportsTo edges: {len(reports_via_edge)}, in Engineering: {len(engineering)}");

        print("\n=== 9. Assign Comprehension on Edge Results ===");
        # Get nodes via edges, then modify them
        if len(all_reports) > 0 {
            # Assign to all employees found via outgoing edges
            all_reports(=department="Updated");
            print(f"Updated department for {len(all_reports)} employees");
        }

        print("\n=== 10. Chained Edge Traversal + Filter + Assign ===");
        # Get nodes, filter them, then modify filtered subset
        targets = [-->];
        if len(targets) > 0 {
            high_earners = targets(?salary >= 75000);
            if len(high_earners) > 0 {
                high_earners(=salary=90000);
                print(f"Gave raise to {len(high_earners)} employees");
            }
        }

        print("\n=== 11. Outgoing Edge Results Only ===");
        # Focus on outgoing edges which return only Employee nodes
        out_edges = [-->];
        print(f"Total outgoing connections: {len(out_edges)}");

        disengage;
    }
}

# ===== Additional Comprehension Patterns =====
with entry {
    print("=== 12. Filter Comprehension Comparison Operators ===");
    nums = [TestObj(x=i) for i in range(10)];

    equal_five = nums(?x == 5);
    not_five = nums(?x != 5);
    greater = nums(?x > 5);
    greater_eq = nums(?x >= 5);
    less = nums(?x < 5);
    less_eq = nums(?x <= 5);

    print(f"x==5: {len(equal_five)}, x!=5: {len(not_five)}");
    print(f"x>5: {len(greater)}, x>=5: {len(greater_eq)}");
    print(f"x<5: {len(less)}, x<=5: {len(less_eq)}");

    print("\n=== 13. Assign with Multiple Attributes ===");
    people = [Person(name=f"Person{i}") for i in range(5)];
    people(=age=25, score=100);
    print(f"Assigned age=25, score=100 to {len(people)} people");
    print(f"First person: age={people[0].age}, score={people[0].score}");

    print("\n=== 14. Empty Collection Handling ===");
    empty = [];
    filtered_empty = empty(?x > 5);
    assigned_empty = empty(=x=10);
    print(f"Filter on empty: {len(filtered_empty)}");
    print(f"Assign on empty: {len(assigned_empty)}");

    print("\n=== 15. Comprehension Return Values ===");
    original = [TestObj(x=i) for i in range(3)];
    filtered = original(?x > 0);
    assigned = original(=y=50);

    print(f"Original list: {len(original)} items");
    print(f"Filtered returns: {len(filtered)} items (new list)");
    print(f"Assigned returns: {len(assigned)} items (same list, modified)");
    print(f"Original[0].y after assign: {original[0].y}");

    print("\n=== Running Walker Demo ===");
    root spawn ComprehensionDemo();

    print("\n✓ All special comprehension variations demonstrated!");
}
"""Filter and assign comprehensions: Filter and assign comprehension syntax."""

import random;

# ===== Object Definitions =====
obj TestObj {
    has x: int = 0,
        y: int = 0,
        z: int = 0;
}

obj Person {
    has name: str,
        age: int = 0,
        score: int = 0;
}

# ===== Node and Edge Definitions for Spatial Comprehensions =====
node Employee {
    has name: str,
        salary: int = 0,
        department: str = "Unknown";
}

edge ReportsTo {
    has years: int = 0;
}

edge Collaborates {
    has project: str = "None";
}

# ===== Walker for Demonstrating Edge Comprehensions =====
walker ComprehensionDemo {
    can demo_basic with `root entry {
        print("=== 1. Basic Filter Comprehension ===");
        # Create objects with varying attributes
        random.seed(42);
        items = [];
        for i=0 to i<10 by i+=1 {
            items.append(TestObj(x=random.randint(0, 20), y=random.randint(0, 20), z=i));
        }

        # Filter comprehension: collection(?condition1, condition2, ...)
        # Filters objects where ALL conditions are true
        filtered = items(?x >= 10, y < 15);
        print(f"Filtered {len(items)} items to {len(filtered)} where x>=10 and y<15");

        print("\n=== 2. Filter with Single Condition ===");
        high_x = items(?x > 15);
        print(f"Items with x > 15: {len(high_x)}");

        print("\n=== 3. Filter with Multiple Conditions ===");
        complex_filter = items(?x >= 5, x <= 15, y > 10);
        print(f"Items with 5 <= x <= 15 and y > 10: {len(complex_filter)}");

        print("\n=== 4. Basic Assign Comprehension ===");
        # Create fresh objects
        objs = [TestObj(x=i) for i in range(3)];
        print(f"Before assign: x values = {[o.x for o in objs]}");

        # Assign comprehension: collection(=attr1=val1, attr2=val2, ...)
        # Sets attributes on ALL objects in collection
        objs(=y=100, z=200);
        print(f"After assign(=y=100, z=200): y values = {[o.y for o in objs]}");

        print("\n=== 5. Chained Filter and Assign ===");
        # Filter THEN assign
        people = [
            Person(name="Alice", age=25, score=80),
            Person(name="Bob", age=30, score=90),
            Person(name="Charlie", age=35, score=70)
        ];

        # Filter people age >= 30, then boost their scores
        people(?age >= 30)(=score=95);
        print("People after filter(age>=30) + assign(score=95):");
        for p in people {
            print(f"  {p.name}: age={p.age}, score={p.score}");
        }

        print("\n=== 6. Multiple Chained Filters ===");
        data = [TestObj(x=i, y=i*2, z=i*3) for i in range(10)];
        # Apply multiple filters in sequence
        result = data(?x > 2)(?y < 15)(?z >= 6);
        print(f"Triple filtered from {len(data)} to {len(result)} items");

        # Build graph for spatial comprehensions
        print("\n=== Building Organization Graph ===");
        mgr = Employee(name="Manager", salary=100000, department="Engineering");
        dev1 = Employee(name="Dev1", salary=80000, department="Engineering");
        dev2 = Employee(name="Dev2", salary=75000, department="Engineering");
        dev3 = Employee(name="Dev3", salary=70000, department="Sales");

        root ++> mgr;
        mgr +>: ReportsTo(years=5) :+> dev1;
        mgr +>: ReportsTo(years=2) :+> dev2;
        mgr +>: ReportsTo(years=1) :+> dev3;
        dev1 <+: Collaborates(project="ProjectX") :+> dev2;

        print("Graph built: Manager -> 3 Devs");

        # Visit employees to demonstrate edge comprehensions
        visit [-->];
    }

    can demo_edge_filters with Employee entry {
        print(f"\n=== At {here.name} ===");

        print("=== 7. Filter Comprehension on Edge Results ===");
        # Edge traversal returns nodes; filter on NODE attributes
        # Syntax: [edge_expr](?node_attribute_filter)

        # Get all direct reports and filter by salary
        all_reports = [-->];
        high_paid = all_reports(?salary > 75000);
        print(f"Direct reports: {len(all_reports)}, high paid (>75k): {len(high_paid)}");

        print("\n=== 8. Typed Edge with Node Filter ===");
        # Traverse specific edge type, filter resulting nodes
        reports_via_edge = [->:ReportsTo:->];
        engineering = reports_via_edge(?department == "Engineering");
        print(f"ReportsTo edges: {len(reports_via_edge)}, in Engineering: {len(engineering)}");

        print("\n=== 9. Assign Comprehension on Edge Results ===");
        # Get nodes via edges, then modify them
        if len(all_reports) > 0 {
            # Assign to all employees found via outgoing edges
            all_reports(=department="Updated");
            print(f"Updated department for {len(all_reports)} employees");
        }

        print("\n=== 10. Chained Edge Traversal + Filter + Assign ===");
        # Get nodes, filter them, then modify filtered subset
        targets = [-->];
        if len(targets) > 0 {
            high_earners = targets(?salary >= 75000);
            if len(high_earners) > 0 {
                high_earners(=salary=90000);
                print(f"Gave raise to {len(high_earners)} employees");
            }
        }

        print("\n=== 11. Outgoing Edge Results Only ===");
        # Focus on outgoing edges which return only Employee nodes
        out_edges = [-->];
        print(f"Total outgoing connections: {len(out_edges)}");

        disengage;
    }
}

# ===== Additional Comprehension Patterns =====
with entry {
    print("=== 12. Filter Comprehension Comparison Operators ===");
    nums = [TestObj(x=i) for i in range(10)];

    equal_five = nums(?x == 5);
    not_five = nums(?x != 5);
    greater = nums(?x > 5);
    greater_eq = nums(?x >= 5);
    less = nums(?x < 5);
    less_eq = nums(?x <= 5);

    print(f"x==5: {len(equal_five)}, x!=5: {len(not_five)}");
    print(f"x>5: {len(greater)}, x>=5: {len(greater_eq)}");
    print(f"x<5: {len(less)}, x<=5: {len(less_eq)}");

    print("\n=== 13. Assign with Multiple Attributes ===");
    people = [Person(name=f"Person{i}") for i in range(5)];
    people(=age=25, score=100);
    print(f"Assigned age=25, score=100 to {len(people)} people");
    print(f"First person: age={people[0].age}, score={people[0].score}");

    print("\n=== 14. Empty Collection Handling ===");
    empty = [];
    filtered_empty = empty(?x > 5);
    assigned_empty = empty(=x=10);
    print(f"Filter on empty: {len(filtered_empty)}");
    print(f"Assign on empty: {len(assigned_empty)}");

    print("\n=== 15. Comprehension Return Values ===");
    original = [TestObj(x=i) for i in range(3)];
    filtered = original(?x > 0);
    assigned = original(=y=50);

    print(f"Original list: {len(original)} items");
    print(f"Filtered returns: {len(filtered)} items (new list)");
    print(f"Assigned returns: {len(assigned)} items (same list, modified)");
    print(f"Original[0].y after assign: {original[0].y}");

    print("\n=== Running Walker Demo ===");
    root spawn ComprehensionDemo();

    print("\n✓ All special comprehension variations demonstrated!");
}
from __future__ import annotations
from jaclang.runtimelib.builtin import *
from jaclang import JacMachineInterface as _jl
import random

class TestObj(_jl.Obj):
    x: int = 0
    y: int = 0
    z: int = 0

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

class Employee(_jl.Node):
    name: str
    salary: int = 0
    department: str = 'Unknown'

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

class Collaborates(_jl.Edge):
    project: str = 'None'

class ComprehensionDemo(_jl.Walker):

    @_jl.entry
    def demo_basic(self, here: _jl.Root) -> None:
        print('=== 1. Basic Filter Comprehension ===')
        random.seed(42)
        items = []
        i = 0
        while i < 10:
            items.append(TestObj(x=random.randint(0, 20), y=random.randint(0, 20), z=i))
            i += 1
        filtered = _jl.filter(items=items, func=lambda i: i.x >= 10 and i.y < 15)
        print(f'Filtered {len(items)} items to {len(filtered)} where x>=10 and y<15')
        print('\n=== 2. Filter with Single Condition ===')
        high_x = _jl.filter(items=items, func=lambda i: i.x > 15)
        print(f'Items with x > 15: {len(high_x)}')
        print('\n=== 3. Filter with Multiple Conditions ===')
        complex_filter = _jl.filter(items=items, func=lambda i: i.x >= 5 and i.x <= 15 and (i.y > 10))
        print(f'Items with 5 <= x <= 15 and y > 10: {len(complex_filter)}')
        print('\n=== 4. Basic Assign Comprehension ===')
        objs = [TestObj(x=i) for i in range(3)]
        print(f'Before assign: x values = {[o.x for o in objs]}')
        _jl.assign(objs, (('y', 'z'), (100, 200)))
        print(f'After assign(=y=100, z=200): y values = {[o.y for o in objs]}')
        print('\n=== 5. Chained Filter and Assign ===')
        people = [Person(name='Alice', age=25, score=80), Person(name='Bob', age=30, score=90), Person(name='Charlie', age=35, score=70)]
        _jl.assign(_jl.filter(items=people, func=lambda i: i.age >= 30), (('score',), (95,)))
        print('People after filter(age>=30) + assign(score=95):')
        for p in people:
            print(f'  {p.name}: age={p.age}, score={p.score}')
        print('\n=== 6. Multiple Chained Filters ===')
        data = [TestObj(x=i, y=i * 2, z=i * 3) for i in range(10)]
        result = _jl.filter(items=_jl.filter(items=_jl.filter(items=data, func=lambda i: i.x > 2), func=lambda i: i.y < 15), func=lambda i: i.z >= 6)
        print(f'Triple filtered from {len(data)} to {len(result)} items')
        print('\n=== Building Organization Graph ===')
        mgr = Employee(name='Manager', salary=100000, department='Engineering')
        dev1 = Employee(name='Dev1', salary=80000, department='Engineering')
        dev2 = Employee(name='Dev2', salary=75000, department='Engineering')
        dev3 = Employee(name='Dev3', salary=70000, department='Sales')
        _jl.connect(left=_jl.root(), right=mgr)
        _jl.connect(left=mgr, right=dev1, edge=ReportsTo(years=5))
        _jl.connect(left=mgr, right=dev2, edge=ReportsTo(years=2))
        _jl.connect(left=mgr, right=dev3, edge=ReportsTo(years=1))
        _jl.connect(left=dev1, right=dev2, edge=Collaborates(project='ProjectX'), undir=True)
        print('Graph built: Manager -> 3 Devs')
        _jl.visit(self, _jl.refs(_jl.Path(here)._out().visit()))

    @_jl.entry
    def demo_edge_filters(self, here: Employee) -> None:
        print(f'\\n=== At {here.name} ===')
        print('=== 7. Filter Comprehension on Edge Results ===')
        all_reports = _jl.refs(_jl.Path(here)._out())
        high_paid = _jl.filter(items=all_reports, func=lambda i: i.salary > 75000)
        print(f'Direct reports: {len(all_reports)}, high paid (>75k): {len(high_paid)}')
        print('\n=== 8. Typed Edge with Node Filter ===')
        reports_via_edge = _jl.refs(_jl.Path(here)._out(edge=lambda i: isinstance(i, ReportsTo)))
        engineering = _jl.filter(items=reports_via_edge, func=lambda i: i.department == 'Engineering')
        print(f'ReportsTo edges: {len(reports_via_edge)}, in Engineering: {len(engineering)}')
        print('\n=== 9. Assign Comprehension on Edge Results ===')
        if len(all_reports) > 0:
            _jl.assign(all_reports, (('department',), ('Updated',)))
            print(f'Updated department for {len(all_reports)} employees')
        print('\n=== 10. Chained Edge Traversal + Filter + Assign ===')
        targets = _jl.refs(_jl.Path(here)._out())
        if len(targets) > 0:
            high_earners = _jl.filter(items=targets, func=lambda i: i.salary >= 75000)
            if len(high_earners) > 0:
                _jl.assign(high_earners, (('salary',), (90000,)))
                print(f'Gave raise to {len(high_earners)} employees')
        print('\n=== 11. Outgoing Edge Results Only ===')
        out_edges = _jl.refs(_jl.Path(here)._out())
        print(f'Total outgoing connections: {len(out_edges)}')
        _jl.disengage(self)
        return
print('=== 12. Filter Comprehension Comparison Operators ===')
nums = [TestObj(x=i) for i in range(10)]
equal_five = _jl.filter(items=nums, func=lambda i: i.x == 5)
not_five = _jl.filter(items=nums, func=lambda i: i.x != 5)
greater = _jl.filter(items=nums, func=lambda i: i.x > 5)
greater_eq = _jl.filter(items=nums, func=lambda i: i.x >= 5)
less = _jl.filter(items=nums, func=lambda i: i.x < 5)
less_eq = _jl.filter(items=nums, func=lambda i: i.x <= 5)
print(f'x==5: {len(equal_five)}, x!=5: {len(not_five)}')
print(f'x>5: {len(greater)}, x>=5: {len(greater_eq)}')
print(f'x<5: {len(less)}, x<=5: {len(less_eq)}')
print('\n=== 13. Assign with Multiple Attributes ===')
people = [Person(name=f'Person{i}') for i in range(5)]
_jl.assign(people, (('age', 'score'), (25, 100)))
print(f'Assigned age=25, score=100 to {len(people)} people')
print(f'First person: age={people[0].age}, score={people[0].score}')
print('\n=== 14. Empty Collection Handling ===')
empty = []
filtered_empty = _jl.filter(items=empty, func=lambda i: i.x > 5)
assigned_empty = _jl.assign(empty, (('x',), (10,)))
print(f'Filter on empty: {len(filtered_empty)}')
print(f'Assign on empty: {len(assigned_empty)}')
print('\n=== 15. Comprehension Return Values ===')
original = [TestObj(x=i) for i in range(3)]
filtered = _jl.filter(items=original, func=lambda i: i.x > 0)
assigned = _jl.assign(original, (('y',), (50,)))
print(f'Original list: {len(original)} items')
print(f'Filtered returns: {len(filtered)} items (new list)')
print(f'Assigned returns: {len(assigned)} items (same list, modified)')
print(f'Original[0].y after assign: {original[0].y}')
print('\n=== Running Walker Demo ===')
_jl.spawn(_jl.root(), ComprehensionDemo())
print('\n✓ All special comprehension variations demonstrated!')
Jac Grammar Snippet
filter_compr: LPAREN NULL_OK filter_compare_list RPAREN
            | LPAREN TYPE_OP NULL_OK typed_filter_compare_list RPAREN
assign_compr: LPAREN EQ kw_expr_list RPAREN
filter_compare_list: (filter_compare_list COMMA)? filter_compare_item
typed_filter_compare_list: expression (COLON filter_compare_list)?
filter_compare_item: named_ref cmp_op expression

Description

Special Comprehensions - Filter and Assign

Special comprehensions are unique Jac constructs that provide concise filtering and bulk modification operations on collections. These are particularly powerful in Object-Spatial Programming for querying and manipulating nodes retrieved through graph traversal.

Two Forms of Comprehensions

Type Syntax Purpose Returns
Filter collection(?condition1, condition2, ...) Select objects matching ALL conditions New filtered collection
Assign collection(=attr1=val1, attr2=val2, ...) Bulk modify object attributes Same collection (modified)

Basic Filter Comprehension

Line 46 demonstrates the fundamental syntax: filtered = items(?x >= 10, y < 15);

The components are: - items - The collection to filter - ? - Filter operator - x >= 10, y < 15 - Conditions (both must be true) - Conditions reference object attributes directly without prefixes

Filter semantics: 1. For each object in the collection 2. Evaluate ALL conditions against that object's attributes 3. Include object only if ALL conditions are True (AND logic) 4. Return new filtered collection

Line 47 shows the result: filtering 10 items down to those where both x >= 10 AND y < 15.

Single Condition Filter

Line 50 shows filtering with one condition: high_x = items(?x > 15);

Even with a single condition, the ? operator is required. This finds all items where the x attribute exceeds 15.

Multiple Conditions (AND Logic)

Line 54 demonstrates multi-condition filtering: complex_filter = items(?x >= 5, x <= 15, y > 10);

All three conditions must be satisfied: - x >= 5 - Lower bound on x - x <= 15 - Upper bound on x - y > 10 - Constraint on y

Objects are included only when ALL conditions pass. This implements conjunction (AND). For OR logic, use multiple filter calls or combine results.

Comparison Operators

Lines 158-167 demonstrate all available operators:

Operator Example Meaning
== (?x == 5) Attribute equals value
!= (?x != 5) Attribute not equals value
> (?x > 5) Attribute greater than value
>= (?x >= 5) Attribute greater than or equal
< (?x < 5) Attribute less than value
<= (?x <= 5) Attribute less than or equal

Basic Assign Comprehension

Lines 64-65 demonstrate attribute assignment: objs(=y=100, z=200);

The syntax pattern is: - objs - Collection to modify - = prefix - Indicates assign comprehension - y=100, z=200 - Attribute assignments (comma-separated)

Assign semantics: 1. For each object in the collection 2. Set each specified attribute to its value 3. Mutations occur in-place on original objects 4. Return the collection (now modified)

Line 65 confirms this is a mutating operation - the original objects are changed.

Chained Filter and Assign

Line 76 demonstrates powerful composition: people(?age >= 30)(=score=95);

Execution flow: 1. people(?age >= 30) - Filter people with age >= 30 2. Returns filtered subset (Bob and Charlie) 3. (=score=95) - Assign score=95 to filtered objects 4. Only filtered objects are modified

Lines 78-80 show results: only Bob and Charlie (age >= 30) have scores updated to 95, while Alice (age 25) retains her original score of 80. This enables selective bulk updates: "find all X matching condition, then update them."

Multiple Chained Filters

Line 85 shows sequential filtering: result = data(?x > 2)(?y < 15)(?z >= 6);

Each filter narrows results: 1. (?x > 2) - First filter 2. (?y < 15) - Applied to previous result 3. (?z >= 6) - Final filter on remaining items

Order matters - each filter receives output of the previous filter. Line 86 shows the progressive reduction from 10 items to the final filtered count.

Filter on Edge Traversal Results

Lines 115-117 demonstrate the critical spatial programming pattern. This is where comprehensions become essential for OSP: - [-->] traverses outgoing edges, returns target nodes - Result is a collection of nodes (Employee objects) - (?salary > 75000) filters nodes by attribute - Enables declarative spatial queries

graph LR
    A[Current Node] -->|[--&gt;]| B[Get Connected Nodes]
    B --> C[Filter by Attributes]
    C --> D[Filtered Node Collection]

Typed Edge with Node Filter

Lines 121-123 combine edge type filtering with node attribute filtering. Workflow: - [->:ReportsTo:->] - Traverse only ReportsTo-typed edges - Returns nodes connected via those specific edges - (?department == "Engineering") - Filter nodes by attribute

This dual-level filtering (structural + property) is a hallmark of Jac's spatial model.

Assign on Edge Results

Lines 129-130 demonstrate spatial bulk updates. Pattern: 1. Get nodes via edge traversal: [-->] 2. Modify all retrieved nodes in bulk

This enables graph-wide updates: traverse to find nodes, then update them. Common for propagating changes through graph structures.

Complete Spatial Pattern

Lines 137-141 show the quintessential three-step pattern. This can be written as one expression: [-->](?salary >= 75000)(=salary=90000);

The pattern is: navigate, filter, act.

Empty Collection Handling

Lines 177-180 show edge cases. Both comprehensions handle empty collections gracefully without errors.

Return Value Semantics

Lines 184-190 clarify return behavior:

Type Behavior Example
Filter Returns NEW collection (subset) filtered = original(?x > 0)
Assign Returns SAME collection (modified) assigned = original(=y=50)

After assign, original[0].y is 50 - the original objects were mutated, not copied.

Comprehension Composition Patterns

Supported composition patterns based on the examples:

Pattern Syntax Use Case
Filter → Filter coll(?c1)(?c2) Sequential filtering
Filter → Assign coll(?cond)(=attr=val) Filter then modify
Edge → Filter [-->](?cond) Spatial query
Edge → Filter → Assign [-->](?cond)(=attr=val) Complete spatial update

Comparison with Python

Traditional Python list comprehension:

Jac filter comprehension:

Differences: - Jac: More concise, no explicit iteration - Jac: Direct attribute access (no obj. prefix) - Jac: Integrates seamlessly with edge traversal - Jac: Special assign syntax for bulk updates

For assignment, Python requires explicit loop:

Jac:

Use Cases

Filter comprehensions excel at: - Finding specific nodes: [-->](?dept=="Engineering") - Graph queries: "find all X connected to Y where Z" - Selecting subsets: candidates(?score > threshold) - Filtering walker targets: visit [-->](?active==True);

Assign comprehensions excel at: - Bulk state updates: affected_nodes(=processed=True) - Initialization: new_nodes(=status="pending", priority=0) - Propagating changes: downstream(=needs_update=True) - Resetting attributes: session_nodes(=active=False)

Performance Considerations

  • Filter creates new collections (allocation cost)
  • Assign mutates in-place (no allocation)
  • Each chained comprehension iterates the collection
  • Minimize chaining: coll(?a, b, c) better than coll(?a)(?b)(?c)
  • Edge traversal + filter optimized in runtime

Integration with Object-Spatial Programming

Special comprehensions are designed for Jac's spatial model:

  1. Edge traversal returns collections
  2. Comprehensions filter/modify collections
  3. Natural composition creates fluent API

Example: visit [->:Friend:->](?age > 25, score > 80)(=notified=True);

Meaning: "Visit friends over age 25 with score over 80, mark them as notified"

This makes Jac particularly expressive for graph-based computation and declarative spatial queries.