Part VII: Advanced Features#
In this part:
- Error Handling - Try/except/finally, raising exceptions
- Testing - Test blocks, JacTestClient
- Filter and Assign Comprehensions - List/dict comprehensions, typed filters
- Pipe Operators - Forward/backward pipes
This part covers error handling, testing, and advanced operators like comprehensions and pipes. These features work the same as in Python with minor syntax differences (braces instead of colons, semicolons to end statements).
Error Handling#
Jac uses Python's exception model with try/except/finally blocks. The syntax uses braces but the semantics are identical -- catch specific exceptions, optionally capture them with as, and use finally for cleanup that always runs.
1 Try/Except/Finally#
def risky_operation() -> int {
return 42;
}
def cleanup() -> None {
# Cleanup logic here
}
def example() {
try {
result = risky_operation();
} except ValueError as e {
print(f"Value error: {e}");
} except KeyError {
print("Key not found");
} except Exception as e {
print(f"Unexpected: {e}");
} finally {
cleanup();
}
}
2 Raising Exceptions#
def inner_process(data: dict) -> None {
# Process data here
}
def validate(value: int) -> None {
if value < 0 {
raise ValueError("Value must be non-negative");
}
}
def process(data: dict) -> None {
try {
inner_process(data);
} except KeyError as e {
raise ValueError("Invalid data") from e;
}
}
3 Assertions#
def example() {
condition = True;
value = 10;
data: object = "something";
id = 1;
assert condition;
assert value > 0, "Value must be positive";
assert data is not None, f"Data was None for id {id}";
}
Testing#
1 Test Blocks#
test addition_works {
result = add(2, 3);
assert result == 5;
}
test string_operations {
s = "hello";
assert len(s) == 5;
assert "ell" in s;
assert s.upper() == "HELLO";
}
2 Testing Walkers#
test walker_collects_data {
# Setup graph
root ++> DataNode(value=1);
root ++> DataNode(value=2);
root ++> DataNode(value=3);
# Run walker
result = root spawn Collector();
# Verify
assert len(result.reports) == 3;
assert sum(result.reports) == 6;
}
3 Float Comparison#
4 JacTestClient#
For API testing without starting a server:
import from jaclang.testing { JacTestClient }
test api_endpoints {
client = JacTestClient.from_file("main.jac");
# Register and login
client.register_user("test@example.com", "password123");
client.login("test@example.com", "password123");
# Test endpoint
response = client.post("/CreateItem", {"name": "Test"});
assert response.status_code == 200;
assert response.json()["name"] == "Test";
}
5 Running Tests#
# Run all tests
jac test
# Run specific test
jac test --test-name test_addition
# Stop on first failure
jac test --xit
# Verbose output
jac test --verbose
Filter and Assign Comprehensions#
1 Standard Comprehensions#
def example() {
# List comprehension
squares = [x ** 2 for x in range(10)];
# With condition
evens = [x for x in range(20) if x % 2 == 0];
# Dict comprehension
squared_dict = {x: x ** 2 for x in range(5)};
# Set comprehension
strings = ["hello", "world", "hi"];
unique_lengths = {len(s) for s in strings};
# Generator expression
gen = (x ** 2 for x in range(1000000));
}
2 Filter Comprehension Syntax#
Filter collections with ?condition:
node Person {
has age: int,
status: str;
}
node Employee {
has salary: int,
experience: int;
}
def example(people: list[Person], employees: list[Employee]) {
# Filter people by age
adults = people(?age >= 18);
# Multiple conditions
qualified = employees(?salary > 50000, experience >= 5);
# On graph traversal results
friends = [-->](?status == "active");
}
3 Typed Filter Comprehensions#
Filter by type with backtick syntax:
node Dog {
has name: str;
}
node Cat {
has indoor: bool;
}
node Person {
has age: int;
}
def example(animals: list) {
dogs = animals(`?Dog); # By type only
indoor_cats = animals(`?Cat:indoor==True); # Type with condition
people = [-->](`?Person); # On graph traversal
adults = [-->](`?Person:age > 21); # Traversal with condition
}
4 Assign Comprehension Syntax#
Modify all items with =attr=value:
node Person {
has age: int,
verified: bool = False,
can_vote: bool = False;
}
node Item {
has status: str,
processed_at: str;
}
def now() -> str {
return "2024-01-01";
}
def example(people: list[Person], items: list[Item]) {
# Set attribute on all items
people(=verified=True);
# Chained: filter then assign
people(?age >= 18)(=can_vote=True);
# Multiple assignments
items(=status="processed", processed_at=now());
}
Pipe Operators#
1 Forward Pipe#
def transform(x: list) -> list { return x; }
def filter(x: list) -> list { return x; }
def output(x: list) -> list { return x; }
def remove_nulls(x: list) -> list { return x; }
def normalize(x: list) -> list { return x; }
def validate(x: list) -> list { return x; }
def example() {
input = [1, 2, 3];
raw_data = [1, 2, 3];
# Traditional
result = output(filter(transform(input)));
# With pipes
result = input |> transform |> filter |> output;
# More readable data pipeline
cleaned = raw_data
|> remove_nulls
|> normalize
|> validate
|> transform;
}
2 Backward Pipe#
def transform(x: list) -> list { return x; }
def filter(x: list) -> list { return x; }
def output(x: list) -> list { return x; }
def example() {
input = [1, 2, 3];
# Right to left
result = output <| filter <| transform <| input;
}
3 Atomic Pipes (Graph Operations)#
node Item {}
walker DepthFirstWalker {
can visit with Item entry {
print("depth first");
}
}
walker BreadthFirstWalker {
can visit with Item entry {
print("breadth first");
}
}
with entry {
start = Item();
# Depth-first traversal
start spawn :> DepthFirstWalker();
# Breadth-first traversal
start spawn |> BreadthFirstWalker();
}
Learn More#
Tutorials:
- Testing - Write and run tests
Related Reference:
- Part I: Foundation - Core language features
- Part III: OSP - Graph operations