Skip to content

1.0.P4 Pattern: Extending behaviours

Julio Toboso edited this page Oct 27, 2026 · 14 revisions

Pattern 4 · Extending behaviours

$$\color{goldenrod}{\text{AKA: The Paintjob}}$$

We use this pattern when we want a to alter a behaviour. For a later Tag to modify behaviour that already exists instead of simply replacing it.

But first we need to understand what a Shape is.


🔰 Base and Shape: conjunction, not descent

Suppose all Heroes can attack:

class Hero(Tag):
    @Action
    def Attack(agent):
        return "Attack!"

Now we write a new tag, like this:

class Elf(Hero):
    [...]

Look at Elf(Hero). This maybe looks like ordinary class inheritance to you, but TOP gives it a different meaning. Think about it... are all Elves Heroes? I'm sure they can all be heroes, but so can humans, dwarves, orcs... Being an Elf does not make you a Hero. But maybe ALL Heroes WHO ARE Elves share some skills or training in our world.

Hero is the Base.

Elf is a Shape of that Base: it describes the Hero that is also an Elf.

Conceptually:

$$\color{goldenrod}{\text{Hero}} \quad\cap\quad \color{olive}{\text{Elf}} \quad=\quad \color{olivedrab}{\text{Elf(Hero)}}$$

So Elf(Hero) does not mean:

every Elf is a Hero.

It means:

this is the definition of a Hero that is also an Elf.

venn-beta
    set Hero["Hero"]
    set Elf["Elf"]
    union Hero,Elf["Elf(Hero)"]
Loading

In this Venn Diagram we can see clearly that the Hero set is composed by everything inside the circle. The intersection with Elf makes that lemon-like shape on the side. This shape is Elf(Hero). We want to define the gains for this group of agents, and only them.

It is very similar to the set of Red Cars:

venn-beta
    set Car["Car"]
    set Red["Red"]
    union Car,Red["Red(Car)"]
Loading

We are, therefor, not defining all Red things, only the Red Cars differentiated from other cars. That's why I remember this pattern as "The Paintjob". (When I'm feeling funny I'd say "Heroes painted Elf" instead of "Elf Heroes".)

This is why TOP speaks of Bases and Shapes, rather than parents and children.

Applying a Shape follows that geometry:

$$\color{goldenrod}\text{ The Shape cuts a specific region from its Base. }$$
ari = Character("Ari")

Elf(ari)

assert ari in Hero
assert ari in Elf

Hero is applied first. Then Elf shapes what Hero already established.


🎨 Give the Action a paintjob

Pattern 3 (The Lasagna) showed how several layers can build one Record. Actions can compose in the same spirit: take the behaviour already underneath, work on its result, and return a new one. But what if there is no underlaying Action to call? Well, we make sure we have one when we need it by using bases.

An Elf Hero should still be able to attack, but with elven grace.

We do not want to throw away Hero.Attack. We want Elf to work on top of it.

class Elf(Hero):
    @Action
    @Underlay
    def Attack(agent, base_attack):
        return "With elven grace, " + base_attack()

@Underlay selects the contribution previously visible under the same name, and assigns it to the second parameter (base_attack in this case) as a callable.

So:

Elf(ari)

assert ari.Attack() == "With elven grace, Attack!"

The Hero Action has not been destroyed. Elf reads the behaviour underneath and builds on its result.

A compact way to show the constructive flow is:

$$\begin{array}{c c c} \color{goldenrod}{\text{Hero.Attack}} & 🟡⟶ & \color{olive}{\text{Elf.Attack}} \\[4pt] \text{Attack!} && \text{With elven grace, ○} \end{array}$$

The ○ marks the read into the Underlay: a little hole through the current layer to what lies beneath.

In the structural diagram, we use the same idea with arrows going from o to >:

---
title: Ari
---
swimlane-beta TD

    subgraph Elf
        elf_attack("Attack")
    end

    subgraph Hero
        hero_attack("Attack")
    end

    subgraph Character
        name["name: Ari"]
    end

    elf_attack <---o|Underlay| hero_attack

    elf_attack -.- name
    hero_attack -.- name
Loading

The o connection is a constructive overlay: Elf reads and uses the contribution underneath.

Without that read, a new Action would simply replace the visible one.


Add another coat

Now define a Paladin-shaped Hero:

class Paladin(Hero):
    @Action
    @Underlay
    def Attack(agent, underlay):
        return underlay() + " For the oath!"

Ari is already an Elf Hero. Apply Paladin afterward:

Paladin(ari)

assert ari.Attack() == \
    "With elven grace, Attack! For the oath!"

This exposes an important distinction.

Paladin's Underlay is not automatically Hero.Attack.

Hero is Paladin's Base, so it belongs to Paladin's Geometry. But Hero is already active on Ari.

Immediately before Paladin is applied, Ari's visible Attack is the complete Elf layer.

Therefore Paladin captures that Action.

When we apply the Paladin tag we are also cutting the base, taking out the shape of paladin from the base, but we had already cut out a big chunk: everything that wasn't Elf. So what is left is a Paladin Elf Hero.

venn-beta
    set Hero["Hero"]
    set Elf["Elf"]
    set Paladin["Paladin"]
    union Hero,Elf,Paladin["Ari"]
Loading
$$\begin{array}{c c c c c} \color{goldenrod}{\text{Hero.Attack}} & 🟡⟶ & \color{olive}{\text{Elf.Attack}} & 🟢⟶ & \color{royalblue}{\text{Paladin.Attack}} \\[5pt] \text{Attack!} && \text{With elven grace, ⭕️} && \text{⭕️ For the oath!} \end{array}$$

Information flows from the underlay to the overlay:

Structurally:

---
title: Ari
---
swimlane-beta TD

    subgraph Paladin
        paladin_attack("Attack")
        end

    subgraph Elf
        elf_attack("Attack")
        end

    subgraph Hero
        hero_attack("Attack")
        end

    subgraph Character
        name["name: Ari"]
        end

    paladin_attack <---o|Underlay| elf_attack
    elf_attack <---o|Underlay| hero_attack


Loading

This is Action layering.

Each layer receives the behaviour produced so far, gives it its own paintjob, and exposes the result above it.


⚜️ Geometry and layering are different

This distinction is fundamental.

Geometry answers:

Which meanings make up this Shape?

For:

class Elf(Hero):

Hero is a Base of Elf. Elf is based on Hero.

Applying Elf therefore ensures Hero is applied first.

Layering answers:

What is the visible behaviour?

When:

Elf(ari)
Paladin(ari)

Paladin sees Elf's current Attack, because Elf is already part of Ari's Overlay.

So:

$$\color{gold} \text{ Geometry determines expectations. }$$ $$\color{gold} \text{ Overlay determines results. }$$

A Base may provide an Underlay, but an Underlay does not need to come directly from a Base. A Shape paints a Base with additional meaning. An Underlay lets its contributions paint over the behaviour already produced.

That is why Paladin can capture Elf's Attack even though Elf is not Paladin's Base.


🎨 The Paintjob

A Shape specializes the meaning of its Base. An Action marked @Underlay can specialize the behaviour already supplied under the same name.

The two mechanisms are different, but follow the same compositional philosophy.

$$TAKE → PAINT → RETURN$$

At the Tag level:

$$\color{goldenrod}{\text{Base}} \quad\longrightarrow\quad \color{olive}{\text{Shape}}$$

At the Action level:

$$\color{goldenrod}{\text{Underlay}} \quad{\circ}\!\!{\longrightarrow}\quad \color{tomato}{\text{new Action}}$$

The Action receives its previously visible behaviour through the parameter declared by @Underlay. This contribution is captured when the Tag is applied, so adding later layers does not change what was captured.

Each layer can therefore work on what came before without rebuilding it or needing to know where it originated.

This follows a simple design philosophy:

Specialize instead of rebuilding.

Clone this wiki locally