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!");
}
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 |
|
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 |
|
Jac Grammar Snippet
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] -->|[-->]| 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 thancoll(?a)(?b)(?c)
- Edge traversal + filter optimized in runtime
Integration with Object-Spatial Programming
Special comprehensions are designed for Jac's spatial model:
- Edge traversal returns collections
- Comprehensions filter/modify collections
- 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.