Object-Oriented Programming

Classes, objects, methods, and inheritance in Strada.

Overview

Strada's OOP model is inspired by Perl:

Defining a Class

package Dog;

# Constructor
func new(str $name, int $age) scalar {
    my hash %self = {
        "name" => $name,
        "age" => $age
    };
    return bless(\%self, "Dog");
}

# Method
func speak(scalar $self) void {
    say($self->{"name"} . " says woof!");
}

# Getter method
func get_name(scalar $self) str {
    return $self->{"name"};
}

# Setter method
func set_age(scalar $self, int $age) void {
    $self->{"age"} = $age;
}

Creating and Using Objects

func main() int {
    # Create object using constructor
    my scalar $dog = Dog::new("Rex", 3);

    # Call methods with arrow syntax
    $dog->speak();           # "Rex says woof!"

    # Access properties
    my str $name = $dog->get_name();
    say("Dog's name: " . $name);

    # Modify properties
    $dog->set_age(4);
    return 0;
}

The bless() Function

bless() associates a hash reference with a class name, turning it into an object:

# bless(reference, classname) -> object
my hash %data = { "x" => 10 };
my scalar $obj = bless(\%data, "MyClass");

Package Auto-Prefixing

Functions inside a package block are automatically prefixed with the class name:

package Animal;

# This becomes Animal_new
func new(str $name) scalar {
    my hash %self = { "name" => $name };
    return bless(\%self, "Animal");
}

# This becomes Animal_speak
func speak(scalar $self) void {
    say($self->{"name"} . " makes a sound");
}

# Call with either syntax:
# Animal::new("Rex") or $obj->speak()
Auto-Prefix Rules
  • Only applies to simple packages (no :: in name)
  • Skips main function and package main;
  • Skips functions that already look class-prefixed (e.g., Dog_speak)
  • extern functions are NOT auto-prefixed

Inheritance

Implement inheritance by blessing with a derived class name and calling parent methods:

package Animal;

func new(str $name) scalar {
    my hash %self = { "name" => $name };
    return bless(\%self, "Animal");
}

func speak(scalar $self) void {
    say($self->{"name"} . " makes a sound");
}

package Dog;

func new(str $name, str $breed) scalar {
    my hash %self = {
        "name" => $name,
        "breed" => $breed
    };
    return bless(\%self, "Dog");
}

# Override parent method
func speak(scalar $self) void {
    say($self->{"name"} . " barks: Woof!");
}

func get_breed(scalar $self) str {
    return $self->{"breed"};
}

package Cat;

func new(str $name) scalar {
    my hash %self = { "name" => $name };
    return bless(\%self, "Cat");
}

func speak(scalar $self) void {
    say($self->{"name"} . " meows: Meow!");
}

UNIVERSAL Methods

All objects have access to isa() and can() methods:

my scalar $dog = Dog::new("Rex", "German Shepherd");

# Check if object is of a class
if ($dog->isa("Dog")) {
    say("It's a Dog!");
}

# Check if object has a method
if ($dog->can("speak")) {
    $dog->speak();
}

if ($dog->can("fly")) {
    say("Dog can fly");
} else {
    say("Dogs cannot fly");
}

Polymorphism

func make_sound(scalar $animal) void {
    # Polymorphic method call
    $animal->speak();
}

func main() int {
    my scalar $dog = Dog::new("Rex", "Lab");
    my scalar $cat = Cat::new("Whiskers");

    make_sound($dog);  # "Rex barks: Woof!"
    make_sound($cat);  # "Whiskers meows: Meow!"

    # Store in array for iteration
    my array @animals = [$dog, $cat];

    foreach my scalar $animal (@animals) {
        $animal->speak();
    }
    return 0;
}

Function Pointers in Objects

Objects can store function references as properties:

package Button;

func new(str $label, scalar $on_click) scalar {
    my hash %self = {
        "label" => $label,
        "on_click" => $on_click
    };
    return bless(\%self, "Button");
}

func click(scalar $self) void {
    my scalar $handler = $self->{"on_click"};
    $handler->();
}

# Usage
func main() int {
    my scalar $btn = Button::new("Submit", func () {
        say("Button clicked!");
    });

    $btn->click();  # "Button clicked!"
    return 0;
}

Method Chaining

Return $self from methods to enable chaining:

package Builder;

func new() scalar {
    my hash %self = { "parts" => [] };
    return bless(\%self, "Builder");
}

func add(scalar $self, str $part) scalar {
    push($self->{"parts"}, $part);
    return $self;  # Return self for chaining
}

func build(scalar $self) str {
    return join(", ", $self->{"parts"});
}

# Usage with chaining
func main() int {
    my str $result = Builder::new()
        ->add("header")
        ->add("body")
        ->add("footer")
        ->build();

    say($result);  # "header, body, footer"
    return 0;
}

Best Practices

OOP Tips
  • Always use package to define classes
  • Name constructors new by convention
  • First parameter of methods should be scalar $self
  • Store instance data in the blessed hash
  • Use getters/setters for controlled access to properties
  • Return $self from setters for method chaining