Enhanced Object-Oriented Programming#
Jac enhances traditional OOP with cleaner syntax, automatic constructors, enforced access control, and interface/implementation separation.
Key Differences from Python#
| Feature | Python | Jac |
|---|---|---|
| Object definition | class |
obj |
| Attributes | self.attr in __init__ |
has attr: type |
| Constructor | Manual __init__ |
Automatic from has |
| Access control | Convention (_prefix) |
Enforced (:pub, :priv, :protect) |
| Interface separation | Not built-in | .impl.jac files |
The obj Archetype#
Jac uses obj to define objects with data (has) and methods (def).
Basic Object Definition#
obj Pet {
has name: str;
has species: str;
has age: int;
has is_adopted: bool = False; # Default value
def adopt() -> None {
self.is_adopted = True;
print(f"{self.name} has been adopted!");
}
def get_info() -> str {
status = "adopted" if self.is_adopted else "available";
return f"{self.name} is a {self.age}-year-old {self.species} ({status})";
}
}
with entry {
# Automatic constructor from 'has' attributes
pet = Pet(name="Buddy", species="dog", age=3);
print(pet.get_info());
pet.adopt();
}
Output:
Automatic Constructors#
Jac automatically generates constructors from has declarations - no __init__ needed:
obj Point {
has x: float;
has y: float;
has label: str = "unnamed";
}
with entry {
# All these work
p1 = Point(x=1.0, y=2.0);
p2 = Point(x=0.0, y=0.0, label="origin");
print(f"{p2.label}: ({p2.x}, {p2.y})");
}
Post-Initialization with postinit#
For computed attributes that depend on other attributes:
obj Rectangle {
has width: float;
has height: float;
has area: float by postinit;
def postinit() -> None {
self.area = self.width * self.height;
}
}
with entry {
rect = Rectangle(width=5.0, height=3.0);
print(f"Area: {rect.area}"); # Area: 15.0
}
Inheritance#
Create specialized objects from existing ones using parentheses:
obj Animal {
has name: str;
has species: str;
has age: int;
def make_sound() -> None {
print(f"{self.name} makes a sound.");
}
}
obj Dog(Animal) {
has breed: str = "Unknown";
def make_sound() -> None {
print(f"{self.name} barks!");
}
def fetch() -> None {
print(f"{self.name} fetches the ball!");
}
}
obj Cat(Animal) {
has indoor: bool = True;
def make_sound() -> None {
print(f"{self.name} meows!");
}
}
with entry {
dog = Dog(name="Rex", species="dog", age=3, breed="Labrador");
cat = Cat(name="Whiskers", species="cat", age=2);
dog.make_sound(); # Rex barks!
dog.fetch(); # Rex fetches the ball!
cat.make_sound(); # Whiskers meows!
}
Access Control#
Jac enforces access control with explicit modifiers.
Modifiers#
| Modifier | Scope | Use Case |
|---|---|---|
:pub |
Anywhere (default) | Public API |
:priv |
Same object only | Internal state |
:protect |
Object and subclasses | Shared internals |
Public (:pub)#
Accessible from anywhere. This is the default, so :pub is optional:
obj PublicExample {
has :pub name: str; # Explicit public
has value: int; # Implicit public
def :pub get_name() -> str {
return self.name;
}
}
Private (:priv)#
Only accessible within the object itself:
obj BankAccount {
has :pub owner: str;
has :priv balance: float = 0.0;
has :priv pin: str;
def :pub deposit(amount: float) -> None {
self.balance += amount;
print(f"Deposited ${amount}. New balance: ${self.balance}");
}
def :pub withdraw(amount: float, entered_pin: str) -> bool {
if not self.verify_pin(entered_pin) {
print("Invalid PIN");
return False;
}
if amount > self.balance {
print("Insufficient funds");
return False;
}
self.balance -= amount;
return True;
}
def :priv verify_pin(entered: str) -> bool {
return entered == self.pin;
}
def :pub get_balance() -> float {
return self.balance;
}
}
with entry {
acc = BankAccount(owner="Alice", pin="1234");
acc.deposit(100.0);
print(f"Balance: ${acc.get_balance()}");
# acc.balance; # Error - private
# acc.verify_pin; # Error - private
}
Protected (:protect)#
Accessible in the object and all subclasses:
obj Vehicle {
has :pub make: str;
has :pub model: str;
has :protect mileage: int = 0;
has :protect service_history: list[str] = [];
def :protect add_service(service: str) -> None {
self.service_history.append(service);
}
}
obj Car(Vehicle) {
has :pub num_doors: int = 4;
def :pub drive(miles: int) -> None {
self.mileage += miles; # Can access protected
print(f"Drove {miles} miles. Total: {self.mileage}");
}
def :pub service() -> None {
self.add_service("Oil change"); # Can call protected
print("Car serviced!");
}
}
with entry {
car = Car(make="Toyota", model="Camry");
car.drive(100);
car.service();
}
Implementation Files#
Separate interface from implementation using .impl.jac files.
Interface File (calculator.jac)#
obj Calculator {
has precision: int = 2;
def add(a: float, b: float) -> float; # Signature only
def subtract(a: float, b: float) -> float;
def multiply(a: float, b: float) -> float;
def divide(a: float, b: float) -> float;
}
Implementation File (calculator.impl.jac)#
impl Calculator.add {
return round(a + b, self.precision);
}
impl Calculator.subtract {
return round(a - b, self.precision);
}
impl Calculator.multiply {
return round(a * b, self.precision);
}
impl Calculator.divide {
if b == 0.0 {
raise ValueError("Division by zero");
}
return round(a / b, self.precision);
}
Usage#
import from calculator { Calculator };
with entry {
calc = Calculator(precision=3);
print(calc.add(1.111, 2.222)); # 3.333
print(calc.divide(10.0, 3.0)); # 3.333
}
Benefits#
- Clean interfaces: Users see what, not how
- Maintainability: Change implementation without touching interface
- Testing: Mock implementations for testing
- Organization: Keep complex logic separate
Complete Example#
"""Pet shop management system demonstrating OOP concepts."""
obj Pet {
has :pub name: str;
has :pub species: str;
has :pub age: int;
has :priv medical_records: list[str] = [];
has :protect adoption_fee: float = 50.0;
def :pub get_info() -> str {
return f"{self.name} ({self.species}, {self.age} years)";
}
def :priv add_record(record: str) -> None {
self.medical_records.append(record);
}
def :pub vaccinate(vaccine: str) -> None {
self.add_record(f"Vaccinated: {vaccine}");
print(f"{self.name} received {vaccine} vaccine");
}
}
obj Dog(Pet) {
has :pub breed: str = "Mixed";
has :pub trained: bool = False;
def :pub train() -> None {
self.trained = True;
self.adoption_fee *= 1.5; # Access protected
print(f"{self.name} is now trained! Fee: ${self.adoption_fee}");
}
}
with entry {
buddy = Dog(name="Buddy", species="dog", age=2, breed="Golden Retriever");
buddy.train();
buddy.vaccinate("Rabies");
print(buddy.get_info());
}
Summary#
| Concept | Syntax | Purpose |
|---|---|---|
| Object | obj Name { } |
Define data + behavior |
| Attribute | has name: type |
Declare data field |
| Default | has name: type = value |
Default value |
| Post-init | has name: type by postinit |
Computed attribute |
| Method | def name() -> type { } |
Define behavior |
| Inheritance | obj Child(Parent) { } |
Extend object |
| Public | :pub |
Accessible anywhere |
| Private | :priv |
Object-only access |
| Protected | :protect |
Object + subclass access |
| Interface | def name() -> type; |
Signature only |
| Implementation | impl Obj.method { } |
Method body |
Learn More#
| Topic | Resource |
|---|---|
| OOP Fundamentals | Chapter 7: Enhanced OOP |
| Implementation Separation | Chapter 6: Code Organization |
| Spatial Objects | Nodes & Edges (OSP) |
| Type System | Chapter 2: Variables & Types |