Functions and Abilities#
Code Example
Runnable Example in Jac and JacLib
# Functions and Abilities - Comprehensive function and ability syntax
# ===== Part 1: Basic Functions =====
obj BasicMath {
# Function with typed parameters and return type
def add(x: int, y: int) -> int {
return x + y;
}
# Function with only return type (no params)
def get_default -> int {
return 42;
}
# Function with minimal type annotations (object type)
def multiply(a: object, b: object) -> object {
return a * b;
}
}
# ===== Part 2: Static and Access Modifiers =====
obj Calculator {
has instance_name: str = "calc";
# Static function (belongs to class, not instance)
static def square(x: int) -> int {
return x * x;
}
# Static with private access
static def:priv internal_helper(x: int) -> int {
return x + 100;
}
# Public instance method
def:pub public_method(x: int) -> int {
return x * 2;
}
# Protected method
def:protect protected_method -> str {
return "protected";
}
}
# ===== Part 3: Abstract Methods and Forward Declarations =====
obj AbstractCalculator {
# Abstract method - must be implemented by subclasses
def compute(x: int, y: int) -> int abs;
# Forward declaration with params
def process(value: float) -> float;
# Forward declaration with variadic args
def aggregate(*numbers: tuple) -> float;
# Forward declaration with kwargs
def configure(**options: dict) -> dict;
}
obj ConcreteCalculator(AbstractCalculator) {
# Implement abstract method
def compute(x: int, y: int) -> int {
return x - y;
}
}
# ===== Part 4: Implementation Blocks =====
# Implement forward-declared methods using impl
impl AbstractCalculator.process(value: float) -> float {
return value * 1.5;
}
impl AbstractCalculator.aggregate(*numbers: tuple) -> float {
return sum(numbers) / len(numbers) if numbers else 0.0;
}
impl AbstractCalculator.configure(**options: dict) -> dict {
options["configured"] = True;
return options;
}
# ===== Part 5: Variadic Parameters =====
obj Variadic {
# Positional variadic (*args)
def sum_all(*values: tuple) -> int {
return sum(values);
}
# Keyword variadic (**kwargs)
def collect_options(**opts: dict) -> dict {
return opts;
}
# Mixed: regular, *args, **kwargs
def combined(base: int, *extras: tuple, **options: dict) -> dict {
return {
"base": base,
"extras": extras,
"options": options
};
}
}
# ===== Part 6: Async Functions =====
async def fetch_remote(url: str) -> dict {
# Async function returning dict
return {"url": url, "status": "fetched"};
}
async def process_batch(items: list) -> list {
# Async processing
return [item * 2 for item in items];
}
# ===== Part 7: Decorators =====
def logger(func: object) {
print(f"Decorator applied to {func.__name__}");
return func;
}
def tracer(func: object) {
print(f"Tracer applied");
return func;
}
# Single decorator
@logger
def logged_func(x: int) -> int {
return x + 1;
}
# Multiple decorators (applied bottom-up)
@logger
@tracer
def double_decorated(x: int) -> int {
return x * 2;
}
# ===== Part 8: Walker Abilities - Basic Events =====
walker BasicWalker {
has counter: int = 0;
# Entry ability - triggers when walker is spawned
can initialize with entry {
self.counter = 0;
print("BasicWalker: initialized");
}
# Exit ability - triggers when walker finishes
can finalize with exit {
print(f"BasicWalker: done, counter={self.counter}");
}
}
# ===== Part 9: Walker Abilities with Typed Node Context =====
node Person {
has name: str;
has age: int = 0;
}
node City {
has name: str;
has population: int = 0;
}
walker TypedWalker {
has people_visited: int = 0;
has cities_visited: int = 0;
# Ability triggered when visiting root
can start with `root entry {
print("TypedWalker: Starting at root");
visit [-->];
}
# Ability triggered when visiting Person nodes
can handle_person with Person entry {
self.people_visited += 1;
print(f" Visiting person: {here.name}, age {here.age}");
visit [-->];
}
# Ability triggered when visiting City nodes
can handle_city with City entry {
self.cities_visited += 1;
print(f" Visiting city: {here.name}, pop {here.population}");
visit [-->];
}
# Exit ability with context
can report with exit {
print(f"TypedWalker: Visited {self.people_visited} people, {self.cities_visited} cities");
}
}
# ===== Part 10: Multiple Abilities on Same Node Type =====
walker MultiAbilityWalker {
has stage: str = "initial";
can first_pass with Person entry {
if self.stage == "initial" {
print(f" First pass: {here.name}");
self.stage = "processed";
}
}
can second_pass with Person entry {
if self.stage == "processed" {
print(f" Second pass: {here.name}");
}
}
can start with `root entry {
visit [-->];
}
}
# ===== Part 11: Node Abilities =====
node InteractivePerson {
has name: str;
has greeted: bool = False;
# Node ability triggered when TypedWalker visits
can greet_typed with TypedWalker entry {
print(f" {self.name} says: Hello TypedWalker!");
self.greeted = True;
}
# Node ability triggered when MultiAbilityWalker visits
can greet_multi with MultiAbilityWalker entry {
print(f" {self.name} says: Hello MultiAbilityWalker!");
}
}
# ===== Part 12: Async Abilities =====
async walker AsyncWalker {
async can process with entry {
print("AsyncWalker: async processing");
}
async can handle with Person entry {
print(f"AsyncWalker: async handling {here.name}");
visit [-->];
}
}
# ===== Part 13: Abstract Abilities =====
walker AbstractWalker {
# Abstract ability - subclasses must implement
can must_override with entry abs;
}
walker ConcreteWalker(AbstractWalker) {
# Implement abstract ability
can must_override with entry {
print("ConcreteWalker: implemented abstract ability");
}
}
# ===== Part 14: Static Abilities =====
walker StaticAbilityWalker {
has instance_data: int = 0;
# Static ability (rare, but allowed)
static can class_level with entry {
print("Static ability executed");
}
can instance_level with entry {
self.instance_data += 1;
print(f"Instance ability: data={self.instance_data}");
}
}
# ===== Part 15: Ability Execution and Control Flow =====
walker ControlFlowWalker {
has max_depth: int = 2;
has current_depth: int = 0;
can traverse with Person entry {
print(f" Depth {self.current_depth}: {here.name}");
if self.current_depth >= self.max_depth {
print(" Max depth reached - stopping");
disengage;
}
self.current_depth += 1;
visit [-->];
}
can start with `root entry {
visit [-->];
}
}
# ===== Execution and Tests =====
with entry {
print("=== 1. Basic Functions ===");
math = BasicMath();
print(f"add(5, 3) = {math.add(5, 3)}");
print(f"get_default() = {math.get_default()}");
print(f"multiply(4, 7) = {math.multiply(4, 7)}");
print("\n=== 2. Static and Access Modifiers ===");
print(f"Calculator.square(5) = {Calculator.square(5)}");
calc = Calculator();
print(f"calc.public_method(10) = {calc.public_method(10)}");
print("\n=== 3. Abstract Methods ===");
concrete = ConcreteCalculator();
print(f"compute(10, 3) = {concrete.compute(10, 3)}");
print(f"process(2.5) = {concrete.process(2.5)}");
print(f"aggregate(1,2,3,4,5) = {concrete.aggregate(1, 2, 3, 4, 5)}");
print(f"configure(x=1,y=2) = {concrete.configure(x=1, y=2)}");
print("\n=== 4. Variadic Parameters ===");
v = Variadic();
print(f"sum_all(1,2,3,4,5) = {v.sum_all(1, 2, 3, 4, 5)}");
print(f"collect_options(a=1,b=2) = {v.collect_options(a=1, b=2)}");
print(f"combined(10, 20, 30, x=1, y=2) = {v.combined(10, 20, 30, x=1, y=2)}");
print("\n=== 5. Basic Walker Abilities ===");
root spawn BasicWalker();
print("\n=== 6. Walker Abilities with Typed Node Context ===");
# Build graph: root -> alice (Person) -> nyc (City)
# \\-> bob (Person)
alice = Person(name="Alice", age=30);
bob = Person(name="Bob", age=25);
nyc = City(name="NYC", population=8000000);
root ++> alice;
root ++> bob;
alice ++> nyc;
root spawn TypedWalker();
print("\n=== 7. Multiple Abilities on Same Node Type ===");
root spawn MultiAbilityWalker();
print("\n=== 8. Node Abilities ===");
# Create InteractivePerson nodes
charlie = InteractivePerson(name="Charlie");
diana = InteractivePerson(name="Diana");
root ++> charlie;
charlie ++> diana;
root spawn TypedWalker();
print(f"Charlie greeted: {charlie.greeted}");
print("\n=== 9. Ability Execution and Control Flow ===");
# Build chain for depth test
person1 = Person(name="P1", age=20);
person2 = Person(name="P2", age=21);
person3 = Person(name="P3", age=22);
person4 = Person(name="P4", age=23);
root ++> person1;
person1 ++> person2;
person2 ++> person3;
person3 ++> person4;
root spawn ControlFlowWalker();
print("\n=== 10. Concrete Walker from Abstract ===");
root spawn ConcreteWalker();
print("\n✓ Functions and abilities 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 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 |
|
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 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
|
Jac Grammar Snippet
Description
Functions and Abilities in Jac
Functions and abilities are the core computational units in Jac. Functions provide traditional callable operations, while abilities enable Object-Spatial Programming (OSP) through event-driven, type-specific dispatch during graph traversal.
Important: Self Parameter in Methods
Methods in Jac have different self parameter requirements depending on the archetype type:
Archetype Type | Self Parameter | Example |
---|---|---|
class |
Explicit with type annotation | def init(self: MyClass, x: int) |
obj |
Implicit (not in signature) | def init(x: int) |
node |
Implicit (not in signature) | def process() |
edge |
Implicit (not in signature) | def get_weight() -> float |
walker |
Implicit (not in signature) | can collect(item: str) |
Why the difference? class
follows traditional Python class semantics for compatibility, while obj
, node
, edge
, and walker
use Jac's simplified semantics with automatic self handling. Inside method bodies, all methods can access self
to refer to the current instance, regardless of whether self
appears in the parameter list. See class_archetypes.md for detailed examples.
Optional Parentheses for No-Parameter Methods
When defining methods or functions with no parameters, the ()
are optional in Jac:
# With parentheses (traditional)
def increment() { self.count += 1; }
# Without parentheses (Jac style)
def increment { self.count += 1; }
Both are valid. When calling the method, parentheses are still required: obj.increment()
. This syntactic sugar makes method definitions cleaner and more readable.
Basic Functions
Lines 6-8 show a function with typed parameters and return type. Lines 11-13 demonstrate a function with only a return type (no parameters):
Lines 16-18 use the generic object
type when flexible typing is needed:
Static Functions
Lines 26-28 define a static function that belongs to the class, not instances. Call static functions directly on the class: Calculator.square(5)
(line 307).
Access Modifiers
Lines 31-43 show access control tags:
Modifier | Example Line | Visibility |
---|---|---|
:priv |
31-33 | Private to module |
:pub |
36-38 | Public, accessible anywhere |
:protect |
41-43 | Protected, accessible to subclasses |
Access tags can be combined with static
: static def:priv internal_helper
(line 31).
Abstract Methods
Line 49 demonstrates abstract method declaration. The abs
keyword marks it as abstract - subclasses must implement it. Lines 63-65 show implementation in ConcreteCalculator
.
Forward Declarations
Lines 52, 55, 58 show forward declarations - signatures without bodies. Forward declarations allow separating interface from implementation, useful for circular dependencies.
Implementation Blocks
Lines 70-72 implement forward-declared methods using impl
. The pattern: impl ClassName.method_name(params) -> return_type { body }
Variadic Parameters
Lines 86-88 show positional variadic (args). Lines 91-93 show keyword variadic (*kwargs):
Lines 96-102 combine regular parameters, args, and *kwargs:
Parameter order must be: regular, *args, kwargs**
Async Functions
Lines 106-108 show async function declaration. Async functions enable concurrent operations and must be awaited when called.
Decorators
Lines 128-130 show single decorator application. Lines 134-137 demonstrate multiple decorators (applied bottom-up):
Equivalent to: double_decorated = logger(tracer(double_decorated))
Walker Abilities - Basic Events
Lines 145-147 show an entry ability that triggers when a walker spawns. Lines 151-153 show an exit ability that triggers when the walker completes:
Abilities use can
instead of def
and specify event clauses with with
.
Walker Abilities with Typed Node Context
Lines 172-175 show root-specific ability. Lines 178-182 show typed node ability (triggers only for Person nodes):
The here
reference accesses the current node being visited.
Execution Flow for TypedWalker (line 338)
flowchart TD
Start([Walker Spawns at Root]) --> RootAbility[start ability executes]
RootAbility --> Visit1[visit [-->] queues Alice, Bob]
Visit1 --> AliceVisit[Visit Alice Person]
AliceVisit --> AliceAbility[handle_person executes]
AliceAbility --> Visit2[visit [-->] queues NYC]
Visit2 --> BobVisit[Visit Bob Person]
BobVisit --> BobAbility[handle_person executes]
BobAbility --> NYCVisit[Visit NYC City]
NYCVisit --> NYCAbility[handle_city executes]
NYCAbility --> ExitAbility[report exit ability]
ExitAbility --> Done([Walker Complete])
Multiple Abilities on Same Node Type
Lines 201-212 demonstrate multiple abilities for the same node type:
Both abilities execute sequentially in definition order. Walker state persists across both.
Node Abilities
Lines 225-228 show abilities defined on nodes (not walkers). When a TypedWalker visits, both the walker's ability AND the node's ability execute. The self
in node abilities refers to the node.
Async Abilities
Lines 237-240 show async walker and async abilities:
Abstract Abilities
Line 251 declares abstract ability. Lines 256-258 show implementation in subclass:
Static Abilities
Lines 266-268 show static abilities (rare but allowed). Static abilities belong to the walker class, not instances. Cannot access self
.
Ability Control Flow
Lines 284-287 demonstrate disengage
for early termination. The disengage
statement immediately terminates walker execution.
Ability Event Clauses
Event Clause | Triggers When | Example Line |
---|---|---|
with entry |
Walker spawns or any node visit | 145 |
with exit |
Walker completes | 151 |
with \ root entry` |
Visiting root node specifically | 172 |
with NodeType entry |
Visiting specific node type | 178, 185 |
with (Type1, Type2) entry |
Visiting any of the listed types | See typed_context_blocks_(osp).jac:80 |
with WalkerType entry |
Node ability for specific walker | 225, 231 |
Note: You can specify multiple types in parentheses to trigger the ability for any of those types. For example, can handle with (Person, City) entry
will trigger when visiting either Person or City nodes. See typed_context_blocks_(osp).md for detailed examples of using multiple types with typed context blocks.
Execution Order When Walker Visits Node
- All matching walker abilities execute (in definition order)
- All matching node abilities execute (in definition order)
- Walker processes any
visit
statements to queue next nodes - Walker moves to next queued node or triggers
exit
abilities
Function vs Ability Comparison
Feature | Function (def ) |
Ability (can ) |
---|---|---|
Keyword | def |
can |
Context | Objects, classes, walkers, global | Walkers, nodes |
Invocation | Explicit call | Event-driven (automatic) |
Event clause | None | Required (with entry , etc.) |
OSP role | Traditional computation | Spatial computation |
here reference |
Not available | Available in spatial context |
visitor reference |
Not available | Available in node abilities |
Type-Driven Dispatch Pattern
flowchart TD
WalkerVisit([Walker Visits Node]) --> CheckType{Node Type?}
CheckType -->|Person| PersonAbility[Execute Person abilities]
CheckType -->|City| CityAbility[Execute City abilities]
CheckType -->|Other| GenericAbility[Execute generic abilities]
PersonAbility --> NodeCheck{Node has<br/>abilities?}
CityAbility --> NodeCheck
GenericAbility --> NodeCheck
NodeCheck -->|Yes| NodeAbility[Execute node abilities]
NodeCheck -->|No| Continue
NodeAbility --> Continue[Continue traversal]
Continue --> Next([Next node or exit])
Practical Patterns
Search with disengage:
Multi-stage processing:
Node-walker interaction:
Polymorphic traversal:
Key Insights
- Type-driven dispatch: Walker abilities are selected based on node types, enabling polymorphic behavior
- Separation of concerns: Multiple abilities on same node type allow separating processing stages
- Bidirectional interaction: Walkers have node-specific behavior, nodes have walker-specific behavior
- Event-driven execution: Abilities trigger automatically based on graph traversal events
- State preservation: Walker attributes persist across all ability executions
- Implementation separation: Forward declarations and impl blocks separate interface from implementation
- Rich type system: Abstract methods, access modifiers, async support, and decorators provide full OOP features
Functions provide traditional OOP computation, while abilities enable the unique "computation flows to data" paradigm of Object-Spatial Programming through event-driven, type-specific dispatch during graph traversal.