Composition

Instead of inheritance hierarchies, Coco uses composition—you build complex types by embedding simpler ones. It's more flexible and easier to understand.

Embedding Types

Embed a type by including it without a field name:

type Animal {
    name string
    age i32

    fn speak {
        echo "Some sound";
    }
}

type Dog {
    Animal        // Embedded
    breed string
}

$dog = Dog {
    Animal: Animal { name: "Rex", age: 3 },
    breed: "Labrador"
};

// Access embedded fields directly
echo $dog.name;   // Rex
echo $dog.age;    // 3
echo $dog.breed;  // Labrador

The embedded type's fields become accessible directly on the outer type.

Method Forwarding

Methods from embedded types are available on the outer type:

type Animal {
    name string
    age i32

    fn introduce {
        echo "I'm {$this.name} and I'm {$this.age} years old";
    }
}

$dog = Dog {
    Animal: Animal { name: "Rex", age: 3 },
    breed: "Labrador"
};

$dog.introduce();  // I'm Rex and I'm 3 years old

Overriding Methods

Define a method with the same name to override:

type Animal {
    name string

    fn speak {
        echo "Some animal sound";
    }
}

type Dog {
    Animal
    breed string

    fn speak {
        echo "Woof! I'm {$this.name}";
    }
}

$dog = Dog { Animal: Animal { name: "Rex" }, breed: "Labrador" };
$dog.speak();  // Woof! I'm Rex

To call the embedded type's method:

type Dog {
    Animal
    breed string

    fn speak_twice {
        $this.Animal.speak();  // Call Animal's speak
        $this.speak();         // Call Dog's speak
    }
}

Multiple Embedding

Embed multiple types:

type Named {
    name string
}

type Aged {
    age i32
}

type Person {
    Named
    Aged
    occupation string
}

$person = Person {
    Named: Named { name: "Alice" },
    Aged: Aged { age: 30 },
    occupation: "Engineer"
};

echo $person.name;        // Alice
echo $person.age;         // 30
echo $person.occupation;  // Engineer

Handling Conflicts

If embedded types have fields with the same name, you must access them explicitly:

type A {
    value i32
}

type B {
    value string
}

type Both {
    A
    B
}

$both = Both {
    A: A { value: 42 },
    B: B { value: "hello" }
};

// $both.value would be ambiguous
echo $both.A.value;  // 42
echo $both.B.value;  // hello

Composition vs Inheritance

Why composition over inheritance?

Flexibility. You can compose types in any combination. Inheritance locks you into a hierarchy.

// With inheritance, you'd need:
// Animal -> Mammal -> Dog
// Animal -> Bird -> Penguin
// But what about a Platypus? It's a mammal that lays eggs.

// With composition:
type Platypus {
    Mammal
    EggLayer
}

Explicit relationships. You always know where fields and methods come from.

No diamond problem. Multiple embedding is straightforward—conflicts are explicit.

Easy refactoring. You can change composition without breaking a class hierarchy.

Delegation Pattern

When you want to forward specific methods rather than all of them:

type Logger {
    prefix string

    fn log($message string) {
        echo "[{$this.prefix}] {$message}";
    }
}

type Service {
    logger Logger
    // other fields

    fn log($message string) {
        $this.logger.log($message);  // Delegate to logger
    }
}

$service = Service {
    logger: Logger { prefix: "Service" }
};

$service.log("Started");  // [Service] Started

This gives you control over what's exposed.

Has-A vs Is-A

Composition represents "has-a" relationships:

type Car {
    engine Engine    // Car has an engine
    wheels [4]Wheel  // Car has wheels
}

Use traits for "is-a" or "can-do" relationships:

trait Drivable {
    fn drive;
    fn stop;
}

type Car {
    engine Engine
    wheels [4]Wheel

    @Drivable
    fn drive {
        $this.engine.start();
        // ...
    }

    @Drivable
    fn stop {
        // ...
    }
}

Building Complex Types

Real-world example:

type Timestamps {
    created_at DateTime
    updated_at DateTime
}

type SoftDelete {
    deleted_at ?DateTime

    fn is_deleted: bool {
        return $this.deleted_at != null;
    }

    fn delete {
        $this.deleted_at = Some(DateTime::now());
    }
}

type User {
    Timestamps
    SoftDelete
    id i32
    name string
    email string
}

// User has all the fields and methods from both
$user = User {
    Timestamps: Timestamps {
        created_at: DateTime::now(),
        updated_at: DateTime::now()
    },
    SoftDelete: SoftDelete { deleted_at: null },
    id: 1,
    name: "Alice",
    email: "alice@example.com"
};

$user.delete();
echo $user.is_deleted();  // true

Constructor Patterns

For composed types, constructors often create embedded parts:

type User {
    Timestamps
    SoftDelete
    id i32
    name string
    email string

    fn new($name string, $email string): User {
        $now = DateTime::now();
        return User {
            Timestamps: Timestamps {
                created_at: $now,
                updated_at: $now
            },
            SoftDelete: SoftDelete { deleted_at: null },
            id: generate_id(),
            name: $name,
            email: $email
        };
    }
}

$user = User::new("Alice", "alice@example.com");

Best Practices

Compose for reuse. If multiple types need the same fields and behavior, extract them into an embeddable type.

Keep embedded types focused. Timestamps should just be timestamps, not timestamps plus audit logging plus versioning.

Be explicit about delegation. Don't embed just to get all methods forwarded—embed when the relationship is meaningful.

Use traits for behavior contracts. Embedding is for implementation reuse. Traits are for shared interfaces.

Name embedded types clearly. Timestamps, SoftDelete, Auditable—the name should indicate what behavior it adds.

Copyright (c) 2025 Ocean Softworks, Sharkk