Typed context blocks (OSP)#
Code Example
Runnable Example in Jac and JacLib
"""Typed context blocks: Conditional code execution based on node/walker types."""
# Inheritance chain for products
node Product {
has price: float;
}
node Media(Product) {
has title: str;
}
node Book(Media) {
has author: str;
}
node Magazine(Media) {
has issue: int;
}
node Electronics(Product) {
has name: str;
has warranty_years: int;
}
# Inheritance chain for customers
walker Customer {
has name: str;
has total_spent: float = 0.0;
}
walker RegularCustomer(Customer) {
has loyalty_points: int = 0;
}
walker VIPCustomer(Customer) {
has vip_discount: float = 0.15;
}
# Walker that processes different item types
walker ShoppingCart {
has items_count: int = 0;
has total: float = 0.0;
can start with `root entry {
# Create diverse inventory
root ++> Book(title="Jac Programming", author="John Doe", price=29.99);
root ++> Magazine(title="Tech Today", issue=42, price=5.99);
root ++> Electronics(name="Laptop", price=999.99, warranty_years=2);
visit [-->];
}
# Example 1: Base type in signature + typed context blocks for derived types
# Ability accepts Product (base type), so ALL subtypes trigger this ability
can process_item with Product entry {
print(f"[Inheritance] Processing {type(here).__name__}...");
# Typed context block for derived type Book
-> Book {
print(f" -> Book block: '{here.title}' by {here.author}");
}
# Typed context block for derived type Magazine
-> Magazine {
print(f" -> Magazine block: '{here.title}' Issue #{here.issue}");
}
# Typed context block for derived type Electronics
-> Electronics {
print(f" -> Electronics block: {here.name}, {here.warranty_years}yr warranty");
}
self.items_count += 1;
self.total += here.price;
visit [-->];
}
# Example 2: Multiple specific types in tuple (not using inheritance)
# Ability ONLY triggers for the explicitly listed types
can apply_discount with (Book, Magazine) entry {
print(f"[Tuple] Applying media discount to {type(here).__name__}");
# Typed context block differentiates between the tuple members
-> Book {
discount = here.price * 0.10;
print(f" -> Book: 10% off = ${discount}");
}
-> Magazine {
discount = here.price * 0.20;
print(f" -> Magazine: 20% off = ${discount}");
}
visit [-->];
}
}
# Node that responds differently to different walker types
node Checkout {
has transactions: int = 0;
# Ability accepts base type Customer (all subtypes trigger this)
# Typed context blocks select specific types in the inheritance chain
can process_payment with Customer entry {
self.transactions += 1;
# Specific type: RegularCustomer (subtype of Customer)
-> RegularCustomer {
print(f"\nRegular customer: {visitor.name}");
print(f"Total: ${visitor.total_spent}, Points: {visitor.loyalty_points}");
}
# Specific type: VIPCustomer (subtype of Customer)
-> VIPCustomer {
discount = visitor.total_spent * visitor.vip_discount;
final_total = visitor.total_spent - discount;
print(f"\nVIP customer: {visitor.name}");
print(f"Subtotal: ${visitor.total_spent}, Discount: -${discount}, Final: ${final_total}");
}
# Base type: Customer (matches all Customers including subtypes)
-> Customer {
print(f"Processing payment for {visitor.name}");
}
}
}
with entry {
# Demo 1: ShoppingCart walker uses typed context blocks with inheritance
print("=== Demo 1: Product Inheritance Chain ===");
print("Hierarchy: Product -> Media -> Book/Magazine");
print(" Product -> Electronics\n");
cart = ShoppingCart();
root spawn cart;
print(f"\nCart: {cart.items_count} items, Total: ${cart.total}\n");
# Demo 2: Checkout node uses typed context blocks with customer inheritance
print("=== Demo 2: Customer Inheritance Chain ===");
print("Hierarchy: Customer -> RegularCustomer/VIPCustomer\n");
checkout = root ++> Checkout();
# Base type Customer
base_customer = Customer(name="Charlie", total_spent=50.00);
checkout spawn base_customer;
# Subtype RegularCustomer
regular = RegularCustomer(name="Alice", total_spent=89.97, loyalty_points=150);
checkout spawn regular;
# Subtype VIPCustomer
vip = VIPCustomer(name="Bob", total_spent=89.97, vip_discount=0.15);
checkout spawn vip;
}
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 |
|
Jac Grammar Snippet
Description
Typed Context Blocks (OSP)
Typed context blocks are a powerful feature in Jac's Object-Spatial Programming (OSP) model that allow conditional code execution based on the runtime type of nodes or walkers. They enable polymorphic behavior where a single ability can handle multiple types differently.
Syntax
The syntax uses the ->
operator followed by a type name and a code block:
-> TypeName { #* code *# }
The code inside executes only if the runtime type matches.
Two Use Cases
Typed context blocks work in two contexts:
- In Walker Abilities (lines 55-95): Check the type of
here
(the current node) - In Node Abilities (lines 96-117): Check the type of
visitor
(the current walker)
Two Approaches: Inheritance vs. Tuple
Lines 4-23 define an inheritance hierarchy:
Product (base)
├── Media (intermediate)
│ ├── Book (leaf)
│ └── Magazine (leaf)
└── Electronics (leaf)
Approach 1: Base Type with Inheritance (Lines 55-76)
Use a base type in the ability signature to accept ALL subtypes. See line 55: the ability signature is with Product entry
, which means it triggers for Product AND all its subtypes (Book, Magazine, Electronics).
Lines 59-71 show typed context blocks that differentiate between specific subtypes:
- Line 59: -> Book
executes ONLY if here is a Book
- Line 64: -> Magazine
executes ONLY if here is a Magazine
- Line 69: -> Electronics
executes ONLY if here is Electronics
Approach 2: Tuple of Specific Types (Lines 80-95)
Use parentheses to list only the specific types that should trigger the ability. See line 80: the ability signature is with (Book, Magazine) entry
, which means it triggers ONLY for Book and Magazine (NOT Electronics).
Lines 84-92 show typed context blocks differentiating the tuple members:
- Line 84: -> Book
provides Book-specific discount logic
- Line 89: -> Magazine
provides Magazine-specific discount logic
Key Difference:
- with Product entry
: Electronics WILL trigger (inherits from Product)
- with (Book, Magazine) entry
: Electronics will NOT trigger (not in tuple)
Node Abilities with Inheritance Chains
Lines 25-37 define a customer inheritance hierarchy:
Lines 96-117 show the same pattern for visitor
types. The ability signature at line 98 is with Customer entry
, accepting the base type Customer, so ALL subtypes trigger this ability.
Lines 101-115 show typed context blocks for specific customer types:
- Lines 101-103: -> RegularCustomer
handles regular customers
- Lines 106-111: -> VIPCustomer
handles VIP customers with discount calculation
- Lines 114-116: -> Customer
handles all Customers (including subtypes)
The Key Difference:
- In walker abilities: Typed context blocks check here
(the node type)
- In node abilities: Typed context blocks check visitor
(the walker type)
- Both use the same inheritance matching behavior
Execution Flow
When the walker visits a node:
graph TD
A[Walker visits Node] --> B{Ability matches?}
B -->|Yes| C[Execute ability]
B -->|No| D[Skip]
C --> E{Type context matches?}
E -->|Yes| F[Execute typed block]
E -->|No| G[Skip block]
F --> H[Continue]
G --> H
Demo 1: Shopping Cart (Lines 44-96)
The ShoppingCart
walker demonstrates BOTH approaches:
-
Lines 44-51: Creates inventory (Book, Magazine, Electronics - all inherit from Product)
-
Lines 55-76:
process_item
ability - Inheritance approach - Signature:
with Product entry
- Triggers for ALL Product subtypes (Book, Magazine, Electronics)
-
Typed context blocks differentiate:
-> Book
,-> Magazine
,-> Electronics
-
Lines 80-95:
apply_discount
ability - Tuple approach - Signature:
with (Book, Magazine) entry
- Triggers ONLY for Book and Magazine (NOT Electronics)
- Typed context blocks differentiate:
-> Book
,-> Magazine
Output shows:
- [Inheritance]
appears for all 3 products (base type accepts all)
- [Tuple]
appears only for Book and Magazine (explicit types only)
Demo 2: Checkout (Lines 98-117)
The Checkout
node demonstrates typed context blocks for handling different customer types:
- Lines 98-117: Single ability with three typed context blocks
- Lines 101-103:
-> RegularCustomer
block handles regular customers - Lines 106-111:
-> VIPCustomer
block applies VIP discount - Lines 114-116:
-> Customer
block handles base customer type
When different walkers visit the checkout node:
- Customer
walker → triggers Customer block
- RegularCustomer
walker → triggers RegularCustomer AND Customer blocks
- VIPCustomer
walker → triggers VIPCustomer AND Customer blocks
Why Use Typed Context Blocks?
1. Polymorphic Handling
Handle multiple types in a single ability without explicit if-statements. Compare line 175-183 (without typed context blocks) versus line 186-189 (with typed context blocks) - the latter is cleaner and more declarative.
2. Type Safety
The compiler knows the exact type inside the block, providing better type checking and autocomplete.
3. Extensibility
Add new typed context blocks without modifying existing code. See lines 199-204 for an example of adding a new Furniture type.
4. Separation of Concerns
Each type's logic is isolated in its own block, improving readability.
Relationship to Abilities
Typed context blocks complement the ability system:
Feature | Purpose | Example |
---|---|---|
Ability signature | Declares when ability triggers | with Book entry |
Typed context block | Provides type-specific logic | -> Book { } |
You can combine them as shown in lines 55-76 (general ability with typed blocks) or use more specific ability signatures as shown in line 80.
Multiple Typed Context Blocks
You can have multiple typed context blocks in a single ability (see lines 98-117 for example). Code outside the blocks runs for all types, while each typed context block runs only when its type matches.
Inheritance and Type Hierarchy
Typed context blocks work seamlessly with inheritance. Lines 4-23 define the Product hierarchy.
Two Ways to Handle Subtypes:
1. Base Type in Ability Signature
Line 55 shows with Product entry
- this triggers for Product AND all subtypes (Book, Magazine, Electronics).
2. Tuple of Specific Types
Line 80 shows with (Book, Magazine) entry
- this triggers ONLY for the listed types.
Typed Context Block Matching:
A typed context block -> Product
matches Product and all its subtypes (Book, Electronics, etc.), while -> Book
only matches Book specifically. This is why in the output you see multiple blocks executing for a single node visit.
Best Practice: Check most specific types first if you have multiple blocks, as shown in lines 59-71 where specific types (Book, Magazine, Electronics) are checked before any generic Product handling.
When to Use Typed Context Blocks
Use when: - A single ability needs to handle multiple types differently - You want type-specific logic within a general ability - Adding new types should be easy (extensibility) - You want cleaner code than nested if-statements
Don't use when: - Only handling one type (just use normal ability signature) - Logic is the same for all types (no need for type checking) - Types are unrelated and should have completely separate abilities
Key Insights
- Dual context: Check
here
in walker abilities,visitor
in node abilities - Runtime dispatch: Type checking happens at runtime based on actual instance types
- Optional execution: Blocks only execute when types match, silently skip otherwise
- Complements abilities: Work together with ability signatures for powerful dispatch
- Type-safe: Compiler understands the type inside the block
- Inheritance aware: Blocks match base types and all derived types
Comparison to Traditional OOP
In traditional OOP, you might use:
- Method overloading (compile-time): Different methods for different parameter types
- Visitor pattern (runtime): Double dispatch for type-specific behavior
- Type checking (runtime): isinstance()
checks with if-statements
Typed context blocks provide: - Spatial dispatch: Combines where (node/walker) with what (type) - Declarative syntax: Clear, readable type-specific blocks - Bidirectional: Both nodes and walkers can type-check each other
This is unique to Jac's Object-Spatial Programming model, where computation (walkers) moves through data (nodes/edges), and both can respond based on each other's types.