Skip to content

Names and references#

Code Example

Runnable Example in Jac and JacLib

# Names and References

# self - instance reference
obj Counter {
    has count: int = 0;

    def increment {
        self.count += 1;
        print(self.count);
    }
}

# super - parent reference
obj Animal {
    def speak {
        print("animal sound");
    }
}

obj Dog(Animal) {
    def speak {
        super.speak();
        print("woof");
    }
}

# here - current node reference
node Task {
    has name: str;
}

walker TaskWalker {
    can process with Task entry {
        print(f"at {here.name}");
        visit [-->];
    }
}

# visitor - current walker reference
node Interactive {
    has visitor_name: str = "none";

    can track with TaskWalker entry {
        self.visitor_name = visitor.__class__.__name__;
        print(f"visited by {self.visitor_name}");
    }
}

# root - root node reference
walker RootWalker {
    can start with `root entry {
        print(f"at root: {root}");
        print(f"here is root: {here is root}");
        visit [-->];
    }

    can at_task with Task entry {
        print(f"root is: {root}");
    }
}

# init and postinit
# Note: In 'obj', methods have implicit self (doesn't appear in parameters).
# In 'class', methods require explicit self with type annotation: def init(self: MyClass, ...)
# This example uses 'obj' with implicit self.
obj Configured {
    has value: int;
    has doubled: int = 0;

    def init(value: int) {
        self.value = value;
        self.postinit();
    }

    def postinit {
        self.doubled = self.value * 2;
    }
}

with entry {
    # Test self
    c = Counter();
    c.increment();
    c.increment();

    # Test super
    d = Dog();
    d.speak();

    # Test here and visitor
    task = Task(name="test");
    inter = Interactive();
    root ++> task;
    task ++> inter;
    root spawn TaskWalker();

    # Test root
    root spawn RootWalker();

    # Test init/postinit
    cfg = Configured(value=10);
    print(f"value={cfg.value}, doubled={cfg.doubled}");
}
# Names and References

# self - instance reference
obj Counter {
    has count: int = 0;

    def increment {
        self.count += 1;
        print(self.count);
    }
}

# super - parent reference
obj Animal {
    def speak {
        print("animal sound");
    }
}

obj Dog(Animal) {
    def speak {
        super.speak();
        print("woof");
    }
}

# here - current node reference
node Task {
    has name: str;
}

walker TaskWalker {
    can process with Task entry {
        print(f"at {here.name}");
        visit [-->];
    }
}

# visitor - current walker reference
node Interactive {
    has visitor_name: str = "none";

    can track with TaskWalker entry {
        self.visitor_name = visitor.__class__.__name__;
        print(f"visited by {self.visitor_name}");
    }
}

# root - root node reference
walker RootWalker {
    can start with `root entry {
        print(f"at root: {root}");
        print(f"here is root: {here is root}");
        visit [-->];
    }

    can at_task with Task entry {
        print(f"root is: {root}");
    }
}

# init and postinit
# Note: In 'obj', methods have implicit self (doesn't appear in parameters).
# In 'class', methods require explicit self with type annotation: def init(self: MyClass, ...)
# This example uses 'obj' with implicit self.
obj Configured {
    has value: int;
    has doubled: int = 0;

    def init(value: int) {
        self.value = value;
        self.postinit();
    }

    def postinit {
        self.doubled = self.value * 2;
    }
}

with entry {
    # Test self
    c = Counter();
    c.increment();
    c.increment();

    # Test super
    d = Dog();
    d.speak();

    # Test here and visitor
    task = Task(name="test");
    inter = Interactive();
    root ++> task;
    task ++> inter;
    root spawn TaskWalker();

    # Test root
    root spawn RootWalker();

    # Test init/postinit
    cfg = Configured(value=10);
    print(f"value={cfg.value}, doubled={cfg.doubled}");
}
from __future__ import annotations
from jaclang.runtimelib.builtin import *
from jaclang import JacMachineInterface as _jl

class Counter(_jl.Obj):
    count: int = 0

    def increment(self) -> None:
        self.count += 1
        print(self.count)

class Animal(_jl.Obj):

    def speak(self) -> None:
        print('animal sound')

class Dog(Animal, _jl.Obj):

    def speak(self) -> None:
        super().speak()
        print('woof')

class Task(_jl.Node):
    name: str

class TaskWalker(_jl.Walker):

    @_jl.entry
    def process(self, here: Task) -> None:
        print(f'at {here.name}')
        _jl.visit(self, _jl.refs(_jl.Path(here)._out().visit()))

class Interactive(_jl.Node):
    visitor_name: str = 'none'

    @_jl.entry
    def track(self, visitor: TaskWalker) -> None:
        self.visitor_name = visitor.__class__.__name__
        print(f'visited by {self.visitor_name}')

class RootWalker(_jl.Walker):

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

    @_jl.entry
    def at_task(self, here: Task) -> None:
        print(f'root is: {_jl.root()}')

class Configured(_jl.Obj):
    value: int
    doubled: int = 0

    def __init__(self, value: int) -> None:
        self.value = value
        self.__post_init__()

    def __post_init__(self) -> None:
        self.doubled = self.value * 2
c = Counter()
c.increment()
c.increment()
d = Dog()
d.speak()
task = Task(name='test')
inter = Interactive()
_jl.connect(left=_jl.root(), right=task)
_jl.connect(left=task, right=inter)
_jl.spawn(_jl.root(), TaskWalker())
_jl.spawn(_jl.root(), RootWalker())
cfg = Configured(value=10)
print(f'value={cfg.value}, doubled={cfg.doubled}')
Jac Grammar Snippet
named_ref: special_ref
         | KWESC_NAME
         | NAME

special_ref: KW_INIT
            | KW_POST_INIT
            | KW_ROOT
            | KW_SUPER
            | KW_SELF
            | KW_HERE
            | KW_VISITOR

Description

Jac provides special reference keywords that are automatically available in specific contexts, allowing access to important runtime objects and enabling key Object-Spatial Programming patterns.

What are Special References?

Special references are keywords that Jac makes available automatically in certain contexts. They provide access to important objects like the current instance, parent class, current node, visiting walker, and root node. Understanding when each reference is available is crucial for effective Jac programming.

Reference Availability Table

Reference Object Methods Walker Abilities Node Abilities What It Refers To
self Current instance (object/walker/node)
super Parent class for inheritance
here Current node being visited
visitor Current walker (only in node abilities)
root Root node (persistence anchor)

self - The Instance Reference

Lines 4-11 demonstrate self in an object. Line 8 shows self.count += 1, which accesses the instance's count attribute. Line 9 uses self.count to print the current value.

When you create an instance (line 79) and call its methods (lines 80-81), self refers to that specific instance. Each instance has its own self that refers to itself.

Think of self as the answer to "who am I?" - it always points to the current instance, whether that's an object, a walker, or a node.

super - The Parent Reference

Lines 14-25 demonstrate inheritance with super. The Dog object inherits from Animal (line 20). Line 22 shows super.speak(), which calls the parent class's speak method before adding the dog's own behavior.

classDiagram
    Animal <|-- Dog
    Animal: +speak()
    Dog: +speak()
    Dog: super.speak() calls parent

When d.speak() executes on line 85: 1. Line 22: super.speak() calls Animal's speak (line 15-16) 2. Line 23: Dog adds its own behavior

This allows you to extend parent behavior without replacing it entirely.

here - The Current Node Reference

Lines 28-37 show here used in a walker. Line 34 demonstrates here.name, accessing the current node's attributes.

When a walker visits different nodes: - At root: here refers to the root node - At Task node: here refers to that specific Task node - Line 35: visit [-->] moves to the next node, updating what here points to

graph LR
    A[Root] -->|walker visits| B[Task1]
    B -->|walker visits| C[Task2]

    style A fill:#7b1fa2,stroke:#fff,color:#fff
    style B fill:#1565c0,stroke:#fff,color:#fff
    style C fill:#1565c0,stroke:#fff,color:#fff

    Note1[here = Root]
    Note2[here = Task1]
    Note3[here = Task2]

Think of here as "where am I right now?" - it automatically updates as the walker moves through the graph.

visitor - The Walker Reference (in Node Abilities)

Lines 40-47 demonstrate visitor in a node ability. Line 44 shows visitor.__class__.__name__, accessing the walker's type from within a node's ability.

This creates bidirectional communication: - Walker abilities: Define what the walker does when visiting a node - Node abilities (with visitor): Define what the node does when visited by a walker

The node can inspect the walker using visitor to customize its behavior based on which walker is visiting.

root - The Global Root Reference

Lines 50-60 show root being accessed from different contexts. Line 52 demonstrates printing root, and line 53 checks here is root to see if the current position is the root node.

The root reference is central to Jac's persistence model:

graph TD
    R[root - Persistent]
    A[Node A - Persistent]
    B[Node B - Persistent]
    C[Node C - Temporary]

    R -->|connected| A
    A -->|connected| B

    style R fill:#2e7d32,stroke:#fff,color:#fff
    style A fill:#2e7d32,stroke:#fff,color:#fff
    style B fill:#2e7d32,stroke:#fff,color:#fff
    style C fill:#b71c1c,stroke:#fff,color:#fff

    Note[Anything connected to root persists]

Key insights about root: - Automatic Persistence: Anything connected to root (via edges) persists automatically - Per-User Isolation: Each user gets their own distinct root node - Global Accessibility: Available anywhere in spatial contexts (walkers, node abilities) - No Explicit Save: Just connect to root - persistence happens automatically

init and postinit - Constructor Hooks

Lines 63-75 demonstrate the initialization lifecycle. Line 67 shows def init(value: int), which is the constructor. Line 69 calls self.postinit(), which executes after initialization.

The initialization flow: 1. Object is created: Configured(value=10) on line 98 2. init method executes (lines 67-69), setting self.value 3. postinit method executes (lines 72-73), computing derived values

This two-phase initialization lets you: - init: Set up basic attributes with parameters - postinit: Compute derived values that depend on those attributes

Context Matters: When References Are Available

In walker abilities (lines 12-14, 17-21): - ✓ self - the walker instance - ✓ here - the current node being visited - ✓ root - the root node - ✗ visitor - NOT available (the walker IS the visitor)

In node abilities (lines 43-46): - ✓ self - the node instance (same as here) - ✓ here - also the node instance - ✓ visitor - the walker that's visiting - ✓ root - the root node

In regular object methods (lines 7-10): - ✓ self - the object instance - ✓ super - parent class - ✗ Spatial references (here, visitor, root) - NOT available

Common Patterns

Pattern 1: Walker state accumulation (lines 88-92)

Pattern 2: Node responding to walker (lines 43-46)

Pattern 3: Persistence via root (lines 88-92)

Pattern 4: Parent method delegation (lines 21-24)

Understanding the Execution Model

When root spawn TaskWalker() executes on line 92:

  1. Walker is created and spawned at root
  2. Walker's root entry ability executes with:
  3. self = the TaskWalker instance
  4. here = root node
  5. root = root node (so here is root is True)
  6. visit [-->] queues nodes to visit
  7. Walker visits task node with:
  8. self = still the same TaskWalker instance
  9. here = now the task node (changed!)
  10. root = still the root node
  11. Node's ability executes (if defined) with:
  12. self = the task node
  13. here = the task node (same as self)
  14. visitor = the TaskWalker instance
  15. root = the root node

Why These References Matter

Special references enable Object-Spatial Programming's key features:

  • self: Track instance state as computation moves
  • here: Access data where computation currently is
  • visitor: Let data respond to different computations differently
  • root: Anchor persistence without databases or explicit saves
  • super: Build on existing behavior through inheritance

Understanding when each reference is available and what it refers to is essential for writing effective Jac programs. The references work together to create a programming model where computation flows to data, rather than data flowing to computation.