Classes (Sanctuaries)

In Soul, classes are called “sanctuaries” and provide a blueprint for creating objects.

Basic Class Definition

sanctuary Person {
    soul __init__(name, age) {
        this.name = name
        this.age = age
    }
    
    soul greet() {
        return "Hello, I'm " + this.name
    }
}

// Create an instance
person = Person.new("Alice", 30)
print(person.greet())  // "Hello, I'm Alice"
The __init__ method is the constructor and is called automatically when creating new instances with .new().

Instance Properties

Properties are dynamically added to instances:
sanctuary Car {
    soul __init__(make, model) {
        this.make = make
        this.model = model
        this.speed = 0  // Default value
    }
    
    soul accelerate(amount) {
        this.speed = this.speed + amount
    }
    
    soul getInfo() {
        return this.make + " " + this.model + " at " + this.speed + " mph"
    }
}

myCar = Car.new("Toyota", "Camry")
myCar.accelerate(50)
print(myCar.getInfo())  // "Toyota Camry at 50 mph"

Methods

Methods are functions defined within a sanctuary:
sanctuary Calculator {
    soul __init__() {
        this.result = 0
    }
    
    soul add(n) {
        this.result = this.result + n
        return this  // Enable method chaining
    }
    
    soul multiply(n) {
        this.result = this.result * n
        return this
    }
    
    soul getResult() {
        return this.result
    }
}

calc = Calculator.new()
result = calc.add(5).multiply(3).getResult()  // 15

Inheritance

Classes can inherit from other classes using extends:
sanctuary Animal {
    soul __init__(name) {
        this.name = name
    }
    
    soul speak() {
        return this.name + " makes a sound"
    }
}

sanctuary Dog extends Animal {
    soul __init__(name, breed) {
        super.__init__(name)  // Call parent constructor
        this.breed = breed
    }
    
    soul speak() {
        return this.name + " barks!"
    }
    
    soul wagTail() {
        return this.name + " is wagging tail"
    }
}

dog = Dog.new("Buddy", "Golden Retriever")
print(dog.speak())     // "Buddy barks!"
print(dog.wagTail())   // "Buddy is wagging tail"

Using Super

The super keyword accesses parent class methods:
sanctuary Employee {
    soul __init__(name, id) {
        this.name = name
        this.id = id
    }
    
    soul getInfo() {
        return "Employee: " + this.name + " (ID: " + this.id + ")"
    }
}

sanctuary Manager extends Employee {
    soul __init__(name, id, department) {
        super.__init__(name, id)
        this.department = department
    }
    
    soul getInfo() {
        // Call parent method and extend it
        return super.getInfo() + ", Dept: " + this.department
    }
}

mgr = Manager.new("Alice", "E123", "Engineering")
print(mgr.getInfo())
// "Employee: Alice (ID: E123), Dept: Engineering"

Traits (Oasis)

Traits provide a way to share behavior across multiple classes without inheritance:
oasis Timestampable {
    soul updateTimestamp() {
        this.lastModified = time.now()
    }
    
    soul getLastModified() {
        return this.lastModified || "Never modified"
    }
}

oasis Taggable {
    soul addTag(tag) {
        if (!this.tags) {
            this.tags = []
        }
        this.tags.push(tag)
    }
    
    soul getTags() {
        return this.tags || []
    }
}

sanctuary Document uses Timestampable, Taggable {
    soul __init__(title) {
        this.title = title
    }
}

doc = Document.new("My Report")
doc.addTag("important")
doc.addTag("draft")
doc.updateTimestamp()

print(doc.getTags())  # ["important", "draft"]

Static Methods

Static methods belong to the class itself, not instances:
sanctuary MathUtils {
    static soul pi() {
        return 3.14159
    }
    
    static soul circleArea(radius) {
        return MathUtils.pi() * radius * radius
    }
}

// Call without creating instance
area = MathUtils.circleArea(5)  // 78.53975

// Factory pattern with static methods
sanctuary User {
    soul __init__(name, email) {
        this.name = name
        this.email = email
    }
    
    static soul fromEmail(email) {
        name = email.split("@")[0]
        return User.new(name, email)
    }
}

user = User.fromEmail("john@example.com")
print(user.name)  // "john"

Property Access

Getters and Setters Pattern

sanctuary Temperature {
    soul __init__(celsius) {
        this._celsius = celsius
    }
    
    soul getCelsius() {
        return this._celsius
    }
    
    soul setCelsius(value) {
        if (value < -273.15) {
            throw("Temperature below absolute zero!")
        }
        this._celsius = value
    }
    
    soul getFahrenheit() {
        return (this._celsius * 9/5) + 32
    }
    
    soul setFahrenheit(value) {
        this._celsius = (value - 32) * 5/9
    }
}

temp = Temperature.new(25)
print(temp.getFahrenheit())  // 77
temp.setFahrenheit(86)
print(temp.getCelsius())     // 30

Composition Over Inheritance

Sometimes composition is better than inheritance:
sanctuary Engine {
    soul __init__(horsepower) {
        this.horsepower = horsepower
    }
    
    soul start() {
        return "Engine starting..."
    }
}

sanctuary Radio {
    soul __init__() {
        this.station = "101.5 FM"
    }
    
    soul play() {
        return "Playing " + this.station
    }
}

sanctuary Car {
    soul __init__(model) {
        this.model = model
        this.engine = Engine.new(200)
        this.radio = Radio.new()
    }
    
    soul start() {
        return this.engine.start()
    }
    
    soul playMusic() {
        return this.radio.play()
    }
}

car = Car.new("Sedan")
print(car.start())      // "Engine starting..."
print(car.playMusic())  // "Playing 101.5 FM"

Design Patterns

Singleton Pattern

sanctuary Database {
    static soul getInstance() {
        if (!Database._instance) {
            Database._instance = Database.new()
        }
        return Database._instance
    }
    
    soul __init__() {
        if (Database._instance) {
            throw("Use Database.getInstance()")
        }
        this.connection = "Connected to DB"
    }
}

db1 = Database.getInstance()
db2 = Database.getInstance()
print(db1 == db2)  // true (same instance)

Builder Pattern

sanctuary Pizza {
    soul __init__() {
        this.size = "medium"
        this.toppings = []
    }
}

sanctuary PizzaBuilder {
    soul __init__() {
        this.pizza = Pizza.new()
    }
    
    soul setSize(size) {
        this.pizza.size = size
        return this
    }
    
    soul addTopping(topping) {
        this.pizza.toppings.push(topping)
        return this
    }
    
    soul build() {
        return this.pizza
    }
}

pizza = PizzaBuilder.new()
    .setSize("large")
    .addTopping("cheese")
    .addTopping("pepperoni")
    .build()

Best Practices

Keep Classes Focused

Each class should have a single, well-defined responsibility.

Favor Composition

Use composition when you need to combine behaviors from multiple sources.

Use Traits Wisely

Traits are great for cross-cutting concerns like logging or timestamps.

Encapsulate Data

Use methods to control access to internal state rather than direct property access.