Skip to content

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!");
}
# 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!");
}
from __future__ import annotations
from jaclang.runtimelib.builtin import *
from jaclang import JacMachineInterface as _jl

class BasicMath(_jl.Obj):

    def add(self, x: int, y: int) -> int:
        return x + y

    def get_default(self) -> int:
        return 42

    def multiply(self, a: object, b: object) -> object:
        return a * b

class Calculator(_jl.Obj):
    instance_name: str = 'calc'

    @staticmethod
    def square(x: int) -> int:
        return x * x

    @staticmethod
    def internal_helper(x: int) -> int:
        return x + 100

    def public_method(self, x: int) -> int:
        return x * 2

    def protected_method(self) -> str:
        return 'protected'

class AbstractCalculator(_jl.Obj):

    @abstractmethod
    def compute(self, x: int, y: int) -> int:
        pass

    @_jl.impl_patch_filename('/home/ninja/jaseci/jac/examples/reference/functions_and_abilities.jac')
    def process(self, value: float) -> float:
        return value * 1.5

    @_jl.impl_patch_filename('/home/ninja/jaseci/jac/examples/reference/functions_and_abilities.jac')
    def aggregate(self, *numbers: tuple) -> float:
        return sum(numbers) / len(numbers) if numbers else 0.0

    @_jl.impl_patch_filename('/home/ninja/jaseci/jac/examples/reference/functions_and_abilities.jac')
    def configure(self, **options: dict) -> dict:
        options['configured'] = True
        return options

class ConcreteCalculator(AbstractCalculator, _jl.Obj):

    def compute(self, x: int, y: int) -> int:
        return x - y

class Variadic(_jl.Obj):

    def sum_all(self, *values: tuple) -> int:
        return sum(values)

    def collect_options(self, **opts: dict) -> dict:
        return opts

    def combined(self, base: int, *extras: tuple, **options: dict) -> dict:
        return {'base': base, 'extras': extras, 'options': options}

async def fetch_remote(url: str) -> dict:
    return {'url': url, 'status': 'fetched'}

async def process_batch(items: list) -> list:
    return [item * 2 for item in items]

def logger(func: object) -> None:
    print(f'Decorator applied to {func.__name__}')
    return func

def tracer(func: object) -> None:
    print('Tracer applied')
    return func

@logger
def logged_func(x: int) -> int:
    return x + 1

@logger
@tracer
def double_decorated(x: int) -> int:
    return x * 2

class BasicWalker(_jl.Walker):
    counter: int = 0

    @_jl.entry
    def initialize(self, here) -> None:
        self.counter = 0
        print('BasicWalker: initialized')

    @_jl.exit
    def finalize(self, here) -> None:
        print(f'BasicWalker: done, counter={self.counter}')

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

class City(_jl.Node):
    name: str
    population: int = 0

class TypedWalker(_jl.Walker):
    people_visited: int = 0
    cities_visited: int = 0

    @_jl.entry
    def start(self, here: _jl.Root) -> None:
        print('TypedWalker: Starting at root')
        _jl.visit(self, _jl.refs(_jl.Path(here)._out().visit()))

    @_jl.entry
    def handle_person(self, here: Person) -> None:
        self.people_visited += 1
        print(f'  Visiting person: {here.name}, age {here.age}')
        _jl.visit(self, _jl.refs(_jl.Path(here)._out().visit()))

    @_jl.entry
    def handle_city(self, here: City) -> None:
        self.cities_visited += 1
        print(f'  Visiting city: {here.name}, pop {here.population}')
        _jl.visit(self, _jl.refs(_jl.Path(here)._out().visit()))

    @_jl.exit
    def report(self, here) -> None:
        print(f'TypedWalker: Visited {self.people_visited} people, {self.cities_visited} cities')

class MultiAbilityWalker(_jl.Walker):
    stage: str = 'initial'

    @_jl.entry
    def first_pass(self, here: Person) -> None:
        if self.stage == 'initial':
            print(f'  First pass: {here.name}')
            self.stage = 'processed'

    @_jl.entry
    def second_pass(self, here: Person) -> None:
        if self.stage == 'processed':
            print(f'  Second pass: {here.name}')

    @_jl.entry
    def start(self, here: _jl.Root) -> None:
        _jl.visit(self, _jl.refs(_jl.Path(here)._out().visit()))

class InteractivePerson(_jl.Node):
    name: str
    greeted: bool = False

    @_jl.entry
    def greet_typed(self, visitor: TypedWalker) -> None:
        print(f'    {self.name} says: Hello TypedWalker!')
        self.greeted = True

    @_jl.entry
    def greet_multi(self, visitor: MultiAbilityWalker) -> None:
        print(f'    {self.name} says: Hello MultiAbilityWalker!')

class AsyncWalker(_jl.Walker):
    __jac_async__ = True

    @_jl.entry
    async def process(self, here) -> None:
        print('AsyncWalker: async processing')

    @_jl.entry
    async def handle(self, here: Person) -> None:
        print(f'AsyncWalker: async handling {here.name}')
        _jl.visit(self, _jl.refs(_jl.Path(here)._out().visit()))

class AbstractWalker(_jl.Walker):

    @_jl.entry
    @abstractmethod
    def must_override(self, here) -> None:
        pass

class ConcreteWalker(AbstractWalker, _jl.Walker):

    @_jl.entry
    def must_override(self, here) -> None:
        print('ConcreteWalker: implemented abstract ability')

class StaticAbilityWalker(_jl.Walker):
    instance_data: int = 0

    @staticmethod
    @_jl.entry
    def class_level(self, here) -> None:
        print('Static ability executed')

    @_jl.entry
    def instance_level(self, here) -> None:
        self.instance_data += 1
        print(f'Instance ability: data={self.instance_data}')

class ControlFlowWalker(_jl.Walker):
    max_depth: int = 2
    current_depth: int = 0

    @_jl.entry
    def traverse(self, here: Person) -> None:
        print(f'  Depth {self.current_depth}: {here.name}')
        if self.current_depth >= self.max_depth:
            print('  Max depth reached - stopping')
            _jl.disengage(self)
            return
        self.current_depth += 1
        _jl.visit(self, _jl.refs(_jl.Path(here)._out().visit()))

    @_jl.entry
    def start(self, here: _jl.Root) -> None:
        _jl.visit(self, _jl.refs(_jl.Path(here)._out().visit()))
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 ===')
_jl.spawn(_jl.root(), BasicWalker())
print('\n=== 6. Walker Abilities with Typed Node Context ===')
alice = Person(name='Alice', age=30)
bob = Person(name='Bob', age=25)
nyc = City(name='NYC', population=8000000)
_jl.connect(left=_jl.root(), right=alice)
_jl.connect(left=_jl.root(), right=bob)
_jl.connect(left=alice, right=nyc)
_jl.spawn(_jl.root(), TypedWalker())
print('\n=== 7. Multiple Abilities on Same Node Type ===')
_jl.spawn(_jl.root(), MultiAbilityWalker())
print('\n=== 8. Node Abilities ===')
charlie = InteractivePerson(name='Charlie')
diana = InteractivePerson(name='Diana')
_jl.connect(left=_jl.root(), right=charlie)
_jl.connect(left=charlie, right=diana)
_jl.spawn(_jl.root(), TypedWalker())
print(f'Charlie greeted: {charlie.greeted}')
print('\n=== 9. Ability Execution and Control Flow ===')
person1 = Person(name='P1', age=20)
person2 = Person(name='P2', age=21)
person3 = Person(name='P3', age=22)
person4 = Person(name='P4', age=23)
_jl.connect(left=_jl.root(), right=person1)
_jl.connect(left=person1, right=person2)
_jl.connect(left=person2, right=person3)
_jl.connect(left=person3, right=person4)
_jl.spawn(_jl.root(), ControlFlowWalker())
print('\n=== 10. Concrete Walker from Abstract ===')
_jl.spawn(_jl.root(), ConcreteWalker())
print('\n✓ Functions and abilities demonstrated!')
Jac Grammar Snippet
ability: decorators? KW_ASYNC? (ability_decl | function_decl)

function_decl: KW_OVERRIDE? KW_STATIC? KW_DEF access_tag? named_ref func_decl? (block_tail | KW_ABSTRACT? SEMI)
ability_decl: KW_OVERRIDE? KW_STATIC? KW_CAN access_tag? named_ref event_clause (block_tail | KW_ABSTRACT? SEMI)
block_tail: code_block | KW_BY expression SEMI
event_clause: KW_WITH expression? (KW_EXIT | KW_ENTRY)

func_decl: (LPAREN func_decl_params? RPAREN) (RETURN_HINT expression)?
         | (RETURN_HINT expression)

func_decl_params: (param_var COMMA)* param_var COMMA?
param_var: (STAR_POW | STAR_MUL)? named_ref type_tag (EQ expression)?
         | DIV
         | STAR_MUL

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

  1. All matching walker abilities execute (in definition order)
  2. All matching node abilities execute (in definition order)
  3. Walker processes any visit statements to queue next nodes
  4. 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

  1. Type-driven dispatch: Walker abilities are selected based on node types, enabling polymorphic behavior
  2. Separation of concerns: Multiple abilities on same node type allow separating processing stages
  3. Bidirectional interaction: Walkers have node-specific behavior, nodes have walker-specific behavior
  4. Event-driven execution: Abilities trigger automatically based on graph traversal events
  5. State preservation: Walker attributes persist across all ability executions
  6. Implementation separation: Forward declarations and impl blocks separate interface from implementation
  7. 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.