A defence of multiple inheritance (and things suspeciously close to it).

One day, you wake up with a strange impulse: "I want to make a game". So, you fire up your editor and get typing away. You decide to reach for tools you know well: Lua and LÖVE (just go with okay). This project begins with a few basic functions:

local function hurt(self, amount)
    self.health = self.health + amount
end

local function heal(self, amount)
    self.health = self.health + amount
end

local function attack(self, other)
    other:hurt(self.weapon.power)
end

local function parry(self, other)
    local power_diff = self.weapon.power - self.weapon.power

    if power_diff >= 0 then
        other:hurt(power_diff)
    else
        self:hurt(-power_diff)
    end
end

Also, don't worry if you don't know lua. It's details won't be important.

Each function is designed to be "plug-and-play". The value self is some table. Furthermore, each function operates on self under the assumption everything the function needs will exist at runtime. Then, you implement each class of entity. And while you're at it, you whip together some helper functions.

local PlayerClass =
    { name = "Player"
    , hurt = hurt
    , heal = heal
    , attack = attack
    , parry = parry
    }

local EnemyClass =
    { name = "Enemy"
    , hurt = hurt
    , attack = attack
    , parry = parry
    }

local CrateClass =
    { name = "Crate"
    , hurt = hurt
    }

local function instance(o, class)
    setmetatable(o, { __index = function(t, k) return class[k] end })
    return o
end

local function has_methods(o, methods)
    for _, method in ipairs(methods) do
        if not o[method] or type(o[method]) ~= "function" then
            return false
        end
    end
    return true
end

With all this defined, you can make a list that holds all your game objects. You then write a little for loop to test all the components.

local entities = {
    instance({ health = 10, weapon = { class = "GoodSword", power = 10 }}
            , PlayerClass),
    instance({ health = 10 }
            , CrateClass),
    instance({ health = 10, weapon = { class = "BadSword", power = 10 }}
            , EnemyClass),
    instance({ health = 10 }
            , CrateClass),
    instance({ health = 10 }
            , CrateClass),
    instance({ health = 10, weapon = { class = "BadSword", power = 10 }}
            , EnemyClass),
}


for _, e in pairs(entities) do
    if has_methods(e, {"hurt", "attack"}) then
        print(e.name)
    end
end

Opening up your favorite terminal emulator, you type into it lua main.lua. With little delay, the program spits out exactly what you were hoping to see:

$ lua main.lua
Player
Enemy
Enemy

However, you can't help but get this lingering feeling that something is off. Then, it clicks with you. Lua is dynamically typed. How on earth will you remember all the data types and functions you'll have? The tool you use for Lua autocomplete is just as lost as you are:

local entities: {
    [1]: unknown,
    [2]: unknown,
    [3]: unknown,
    [4]: unknown,
    [5]: unknown,
    [6]: unknown,
}

You find yourself less concerned with "correctness" and "verification" and more worried about one simple fact: games have a lot of functions. It's true, more than you can commit to memory. Without any type information, keeping track of it will be tedious. So, you decide to change tools. Not wanting to deal with the JVM nor wanting to deal with manual memory management, you decided to use C# (again just go with it).

You begin the task of re-writing your code into C#. You start with all your data types:

enum WeaponType { SuperbSword, GoodSword, BadSword, Empty }
class Weapon 
{
    WeaponType Type  { get; set; } = WeaponType.Empty;
    int Power { get { ... } }

    public Weapon(WeaponType type) { ... }
}

class Player 
{
    public string Name { get => "Player"; }
    public int Health { get; set; }
    public Weapon Weapon { get; set; } 

    public Player(int health, Weapon weapon) { ... }
}

class Enemy 
{
    public string Name { get => "Enemy"; }
    public int Health { get; set; }
    public Weapon Weapon { get; set; }

    public Enemy(int health, Weapon weapon) { ... }
}

You even take advantage of C#'s properties to handle how powerful a weapon is. With all those written you, once again, build your list of entities:

var entities = new List<object>
{
    new Player(10, new Weapon(WeaponType.GoodSword)),
    new Crate(1),
    new Enemy(5, new Weapon(WeaponType.BadSword)),
    new Crate(1),
    new Crate(1),
    new Enemy(5, new Weapon(WeaponType.BadSword))
};

But...

foreach (var x in entities) 
{
    Console.WriteLine(x.Name);
}

The .NET compiler kindly points out that it has no idea what the hell you are talking about. "A field called Name in the type object? Yeah, no..." it says. So, you go back and add a parent type to all of them. You call it Entity, since that's what each object is.

class Entity {
    public string Name { get; };
}

class Player : Entity { ... }

C# immediately starts complaining about your code. "Entity.Name is not marked nullable". "Player.Name hides inherited member". You breathe a sigh and move to appease the compiler. Deciding you can't have an empty entity hanging around, you make Entity abstract and add override to Player.Name. Finally, the compiler goes back into slumber. You do the same for all your other classes.

And with a few extra hoops to jump through, you can finally run your for-loop:

var entities = new List<Entity>
{
    new Player(10, new Weapon(WeaponType.GoodSword)),
    new Crate(1),
    new Enemy(5, new Weapon(WeaponType.BadSword)),
    new Crate(1),
    new Crate(1),
    new Enemy(5, new Weapon(WeaponType.BadSword))
};

foreach (var x in entities) 
{
    Console.WriteLine(x.Name);
}

Well, now you need to add all your functions back in. Not wanting to repeat yourself dozens of times, you make another abstract class and-

class Player : Entity, Hurtable { ... }
                    //  |
                    //  +-- Class 'Player' cannot have multiple 
                    //      base classes: 'Entity' and 'Hurtable'

Huh? Oh, alright so no multiple inheritance of classes. Fine, I'll just change Hurtable into IHurtable and make it an interface. Seems like the compiler is satiated. Now do the same for Healable and we get the following:

interface IHurtable 
{
    int Health { get; set; }
    void hurt(int amount) 
    {
        Health -= amount;
    }
}

interface IHealable
{
    int Health { get; set; }
    void heal(int amount)
    {
        Health -= amount;
    }
}

Thankfully C# allows you to have concrete implementations. So, you won't have to implement hurt and heal for every object. However, you will have to include int Health { get; set; } even tho it never changes. A little odd, but you can live with it.

So, now you return to the for-loop. You then want to select objects that are both IHurtable and IHealable. Thankfully C# can filter a list:

var fighters = entities.Where(x => x is IHurtable && x is IWielder);
foreach (var x in fighters) 
{
    Console.WriteLine(x.Name);
}
$ dotnet run
Player
Enemy
Enemy

All is well right? ...right?

Deciding you want to understand what type your list is, you hover over the variable. Much to your dread, your editor tells you it has the following type:

    IEnumerable<Entity>? fighters

Despite only selecting objects that are both IHurtable and IWielder the compiler doesn't narrow the type. It's still Entity. So, you look around for whatever C# calls the map function. Seems like it's Select so you start typing the following:

...
    .Select(x => x as ...);

Wait, you think to yourself, what goes here? You try something like...

.Select(x => x as (IHurtable, IWielder))

C# complains. You try something different.

.Select(x => x as (IHurtable & IWieldr))

C# complains. You try something different.

.Select(x => (IHurtable & IWielder) x)

C# complains. Growing frustrated you turn to Google. Google points you to Stack Overflow. Stack Overflow disappoints you. C#, at the time of writing, cannot create intersection types. What's the solution? Specify an interface inheriting from multiple interfaces and implement that.

// This is fine.
interface INormalFighter : IHealable, IWielder { }

// I'm okay with the events that are unfolding currently
class Player : Entity, IHurtable, IHealable, IWielder, INormalFighter { }

// That's okay, things are going to be okay
class Enemy : Entity, IHurtable, IWielder, INormalFighter

You stare devoid of emotion. "Is this the best statically typed OOP can do"?

Then you feel a wave of disgust hit you as you realize its true horror. You'll have to do this for every intersection. With 3 traits, that's only 7 outcomes. With 4 traits, that becomes 15 interfaces. Plotting this out using a table reveals how fast the number of interfaces grows:

# of Interfaces# of Combinations
11
23
37
415
531
6127

This is exponential growth. You can't possibly handle this. You'll have no idea how many of these interfaces you'll need. And it only gets exponentially worse with each one. Going down this route will kill your game before it even gets started. You let out a sigh, delete the C# project, and return to whittling away at the Lua code. At least editors these days have support for multiple panes, right?

Alright, Enough Story Time

Okay, so I might be melodramatic. But, this is a personal experience I've bumped into many times across many languages. Having to build hierarchies of types/objects, constantly re-implementing small blocks of code, and not being able to freely build any possible view of my data. It's just frustrating.

However, since I used C# as my target example, I'm sure some of you have already scoffed. Maybe cackled, even. "What did you expect from object orient programming languages"? "We've abandoned OOP for a reason". "Have you considered using Rust?"

However, this story is not about inheritance or OOP. It's about composition.

"But you said in the subtitle 'a defense of multiple inheritance'???"

I did.

"You aren't about say..."

Multiple Inheritance by Any Other Name is Just as Sweet

Composition is multiple inheritance. Composition means being about to build up types in parts. Additionally, I should be able to specify that a type has structural dependencies. Finally, I shouldn't have to reimplement the same logic multiple times (looking at you getters and setters). I should be able to freely compose code or types from smaller bits. And most importantly, I should not be limited in how many bits I choose or need to use.

Using multiple bits and pieces like this requires multiple inheritance.

Now, we don't have to use what would traditionally be pointed to as "inheritance". There are a few different ways to inherit multiple traits. Things can have and be multiple things.

Structural Typing

Structural typing is much like the name says: the ability to use the structure of data as a type. A well-known example of a language with structural types is TypeScript. Say you wanted a function called attack, you could implement it as follows:

function hurt(target: {health: number}, amount: number) { ... }

function attack(attacker: {weapon: Weapon}, target: {health: number}) {
    hurt(target, attacker.weapon.power);
}

PureScript also takes after TypeScript in this regard:

hurt :: forall a. {health :: Int | a} -> Int -> {health :: Int | a}
hurt target amount = ...

attack :: forall a b. {weapon :: Weapon | a} -> {health :: Int | b} -> {health :: Int | b}
attack attacker target = hurt target attacker.weapon.power

The inputs to this function are defined in terms of their structure. Attack accepts anything that looks like a {weapon: Weapon} and a {health: number}. This style of structural typing is shared with languages like Haxe. However, other approaches to this exist. For example, in OCaml, objects are typed based on their public methods:

let attack (attacker: <weapon: weapon>) (target: <hurt: int -> unt>) =
    target#hurt attacker#weapon.power

Meanwhile, Go's interfaces are structurally typed. If you embed a struct that implements a method or interface, the host struct also implements that interface.

With generics there are also some more wild things going on, but that's for later.

struct Weapon { ... }
struct HealthTrait { health int; hurtable bool }
struct WeaponTrait { weapon Weapon }

interface Hurtable { 
    Hurt(amount int)
}

func (entity *HealthTrait) Hurt(amount int) { ... }
func (entity *WeaponTrait) Attack(other Hurtable) { ... }


struct Player { HealthTrait; WeaponTrait }
struct Target { HealthTrait }

func main() {
    var player = Player{ ... }
    var target = Target{ ... }
    player.attack(target)
}

At no point did I have to directly implement anything for Player and Target. To the compiler, if it looks like a Duck and sounds like a Duck, it's a Duck. However, unlike dynamically typed languages, we can enjoy tooling that takes advantage of our type declarations.

Not All Sunshine and Roses

Now, this has some downsides. Let's say we have a function and a type as followed:

type Crate = { health : number }
function heal(target: {health: number}, amount: number) { ... }

A Crate has health, but it shouldn't be healable. Moreover, there is no restriction on what things with health heal can be called on. What we gained in flexibility, we lose in type discipline. This issue can also be encountered in Go and OCaml. Additionally, PureScript and OCaml both have their limitations, too.

PureScript has limited ability to refine structural types out of the box. There are libraries such as purescript-option that wrap reflection, unsafe, and FFI to provide more flexible records. However, as it stands, the following would be a challenge to write by oneself:

narrow :: forall r. {|r} -> Maybe {f :: Int | r}
narrow r = ...

OCaml suffers from a similar inability to narrow types. Its object system disallows downcasting. Without downcasting, runtime type information becomes lost. Thus, there is no ergonomic way to restrict the type of objects. Therefore, a function trying to do the following cannot exist:

let narrow : (< >) -> (< f : int >) option = fun obj -> ...

Attempting to implement these functions, is an exercise left to the reader.

With TypeScript, the type checker isn't ridged enough to make sure you are always returning the right type. It will often fall back to "just trust the dev". Which, is fine, I technically have my autocomplete. But I would like the compiler to check I'm not lying or making a mistake.

Checking that I'm not making non-typing mistakes is a different problem.

Structure Requirements

One way to mitigate this is to allow your protocols to specify a specific structure for implementors. This allows for the feel of structural typing while keeping some of the benefits of nominal typing. For example in Scala, traits can define member variables:

trait HurtableTrait extends HealthTrait:
    var health: Int
    def hurt amount = health -= amount

trait HealableTrait extends HealthTrait:
    var health: Int
    def heal amount = health += amount

Both HurtableTrait and HealablTrait require that anyone implementing them must have the field var health: Int. If I wanted to, I could go one step further and extract var health: Int into its own trait. Then, I can have HurtableTrait and HealableTrait derive from HealthTrait:

trait HealthTrait:
    var health: Int

trait HurtableTrait extends HealthTrait:
    def hurt amount = health -= amount

trait HealableTrait extends HealthTrait:
    def heal amount = health += amount

Through scala's traits, I am essentially building up the structure of whatever implements these traits. However, Scala is not the only language with this ability. In C#, I can perform this through properties:

interface HealthTrait {
    int Health { set; get; }
}

interface HurtableTrait : HealthTrait {
    void hurt (int amount) { Health -= amount }
}

interface HealableTrait : HealthTrait {
    void heal(int amount) { Health += amount; }
}

Other languages that share this feature are Dart, Haxe, OCaml, and TypeScript.

However, C#, Dart, and Haxe all still suffers from limitations around intersecting types. And, OCaml has the afformentioned restriction of "no downcasting".

A Moment to Mention C++

Gotta sigma grind set that SEO and talk about everyone.

I don't know how to fit this in, but it loosely fits under structural typing. C++ has a lot of features. Most of which will likely never see much usage. One of those insane features is concepts. They essentially give you a declarative way to constrain templates.

For example, here's a template that requires its inputs to have specific superclasses:

template<typename T, typename U> requires requires (T& attacker, U& target) {
    // Require that attacker implements `WeaponComp`
    typename T::WeaponComp;

    // Require that target implements `HealthComp`
    typename U::HealthComp;
} void attack(T& attacker, U& target) {
    target.hurt(attacker.weapon.power);
}

You can also include other expressions. It gives you a lot of granular control over templates. I can't help but feel that C++30 is going to have a proof assistant. Too bad most C++ is still written like it's '03. But enough about C++, let's talk about other languages. Specifically, languages that left me disappointed.

Languages that Should Do Better

An aside to bash on two darling languages.

Scala and C# are both OOP. And for a loud subset of people, if OOP has it, it's bad. A code smell. A relic of the past. However, specifying higher requirements shouldn't be limited to OOP languages. If we can constrain functions and can constrain types, why couldn't we constrain implementations?

Being able to define a protocol is a core ability in most languages. Especially in languages bosting about "type safety". Adding requirements on the structure of the data would only further this goal. However, two languages, in particular, seem to have a perplexing lack of this feature: Haskell and Rust.

Haskell

The Strong, The Static, The Stiff

Haskell is often lauded for its powerful type system. Yet poking at the language for a few minutes, one can quickly run into the stiffness of Haskell. So, let me present you with some data types:

data Player = Player { health :: Int, weapon :: Weapon }
data Enemy  = Enemy  { health :: Int, weapon :: Weapon }

Haskell will immediately start complaining. See the way records are implemented, health and weapon corresponds to functions Haskell will generate for us. Therefore, the actual type names don't matter and will cause a cascade of issues around ambiguity. Even using `{-# LANGUAGE DuplicateRecordFields #-}, it just becomes a mess. To resolve this, we can change the fields to the following:

data Player = Player { playerHealth :: Int, playerWeapon :: Weapon }
data Enemy  = Enemy  { enemeyHealth :: Int, enemyWeapon :: Weapon }

So, now Player and Enemy have different interfaces. Thus, I have to define a typeclass that provides a unified getter and setter (sigh). This will provide me with a generic health, weapon, and updateHealth function. This is what it looks like after I implement everything:

{- Traits defining getters and setters -}
class HealthTrait a where
    health :: a -> Int
    updateHealth :: a -> Int -> a

class Wielder a where
    weapon :: a -> Weapon

{- Implement Player -}
instance HealthTrait Player where
    health = playerHealth
    updateHealth p amount = p { playerHealth = amount }

instance Wielder Player where
    weapon = playerWeapon

{- Implement Enemy -}
instance HealthTrait Enemy where
    health = enemyHealth
    updateHealth e amount = e { enemyHealth = amount }

instance Wielder Enemy where
    weapon = enemyWeapon

Aaaand, it is almost like a step backward. Not only do I have to have different names for the fields representing the same idea, I still have to implement getters and setters like it's Java. On top of that, Dynamic in Haskell only supports monomorphic types. So, there'd be no way to write the following:

narrow :: (HealthTrait a, Wielder a) => Dynamic -> Maybe a
narrow = {- You can't do it, and don't you try -}

There are libraries that implement polymorphic Dynamic, but... sigh. Let's just move on to the next language before this turns into "Why Capital Doesn't Like Haskell That Much".

Rust

The Rising Upcomer's Shortcoming

Rust, like Haskell, is known for having a powerful type system. Additionally, it has a built-in macro system to boot.

Yes I know Template Haskell exist, but all of haskell's extensions are off by default. (And, I thought it was a different programming language.)

Furthermore, there are now proc-macros that can perform even more advanced transformations. However. Rust has one shortcoming with its type systems: traits.

Traits are Rust's solution to providing protocols on data. They even allow for default implementation much like Java and C#. But, they fall flat in one use case:

trait HealthTrait {
    health: i64;
}

That is illegal rust. The compiler doesn't even understand what you are trying to do here. However, I the human (with a real brain), do understand: traits cannot specify associated member values. Now, it might sound like I am being overly harsh here. And, I am. I'm frustrated as Rust has a close sibling of this feature: associated types.

Without associated member values, Rust is doomed to follow the getter and setter paradigm. Furthermore, it means that accessors can never have default implementations. So, until this issue gets movement, Rust will be cursed to have the dreaded duplicated getter/setter pattern:

trait HealthTrait {
    fn health(&self) -> &i64;
    fn mut_health(&mut self) -> &mut i64;
}

impl HealthTrait for Player {
    fn health(&self) -> &i64 { &self.health }
    fn mut_health(&mut self) -> &mut i64 { &mut self.health }
}

impl HealthTrait for Enemy {
    fn health(&self) -> &i64 { &self.health }
    fn mut_health(&mut self) -> &mut i64 { &mut self.health }
}

You can clean this up with more advanced usage of traits and macros. But, the point still stands, all this could be eliminated if Rust could do this:

trait HealthTrait {
    let Self::health: i64;

    fn health(&self) -> &i64 { &self.health }
    fn mut_health(&mut self) -> &mut i64 { &mut self.health }
}

impl HealthTrait for Player { }
impl HealthTrait for Enemy  { }

Mixins

Alright, I can hopefully keep this part short. Throughout this post, I've been using what can be collectively described as mixins. Most languages call them "default implementation". Mixins allow you to write a blob of code in isolation. That blob can later be integrated with some larger entity.

Rust, C#, Scala, Java, Haxe, and more all use the default implementation approach. Which generally, looks as followed:

interface Foobar {
    int foo(int x, int y){
        return x + y;
    }
}

In OCaml, it just uses plan inheritance:

class virtual heal_trait = object ... end
class virtual hurt_trait = object ... end
class palyer = object
    inherit heal_trait
    inherit hurt_trait
end

This allows you to have a concrete implementation that can be pulled into the target type. It's a great way to reduce code duplication as those that need specialization, can replace the defaults. And those that don't, can just use the defaults. It's a great system. Mixins allow you to compose code together, but how could you compose types? Well, that's where intersections come in.

Type Intersections

T: This & That & Those & These

Type intersections allow you to specify multiple constraints on a parameter. This can either be done directly using ad-hoc intersections or indirectly through generics. For example, Scala allows you to directly state what types you want to intersect on:

def Parry(target: Hurtable & Wielder, attacker: Hurtable & Wielder) = ...

Furthermore, scala allows for type intersection on runtime type information:

val entities: List[Entity] = List(new Player(...), new Enemy(...), ...)
val vulnerableFighters[Hurtable & Wielder] = entities.collect {
    case x: (Hurtable & Wielder) => x
}

This gives the programmer the ability to filter data by what traits they have. Additionally, intersection types can reduce the need for defining intermediate interfaces. Typescript and <squints> PHP (???) allow for this style of ad-hoc intersections.

Other languages limit their existence to generics. For example in Rust and Java, this is how you intersect on implementations:

trait Hurtable { ... }
trait Wielder  { ... }

fn intersection<T, A>(target: T, attacker: A)
where
    T: Hurtable + Wielder,
    A: Hurtable + Wielder,
{ ... }
interface Hurtable{ ... }
interface Wielder { ... }

class Main {
    static <T extends Hurtable & Wielder, A extends Hurtable & Wielder>
        void parry(T target, A attacker) { ... }
}

Go and Haskell also implements intersectional types similar to Java and Rust.

The funny thing about Java being in this section is that both Dart and Kotlin lack this feature. The reason? Because Java. And guess who know has intersection types now? Java.

Runtime Type Information

A lot of these features rely on having access to type information at runtime. For example, from a previous snippet of Scala, this expression can be impossible in several languages:

val entities: List[Entity] = List(new Player(...), new Enemy(...), ...)
val vulnerableFighters[Hurtable & Wielder] = entities.collect {
    case x: (Hurtable & Wielder) => x
}

This code creates a list of type Entity and then filters it. Additionally, this filter asserts that anything that passes the check meets the requirement of Hurtable & Wielder. Scala can narrow the type of x from Entity to Hurtable & Wielder. This allows us to enjoy the flexibility of runtime type information while having static checking on it.

Languages with restricted ability to manipulate runtime information (e.g. Haskell, Rust, OCaml), are more likely to need an ECS approach. Instead of associating traits directly to entities, traits are associated by some sort of ID tag. Then each trait is stored in a specialized container.

However, ECS can easily over complicate design by their extreme seperation of data and state. While cool, not everything fits the ECS model. Tho, if you need preformance above all else, ECS is probably going to get you there.

The Long Over Due End

I will assume that for a lot of people reading this, it doesn't resonate. And, I mean, tons of code was written without extensive usage of the things I just laid out. However, I just feel let down when languages make the promise of composability, and I can't recompose types. Composing functions and types are only half of the story. Composition should also include composing data and implementations.

This means being able to specify details from multiple traits. Allowing our types to define what requirements are needed to implement them. Being able to write default implementations against a required structure. Until more languages include these features, we'll be forever stuck writing boilerplate. Forced to develop carpel tunnel as we write line after line of get_this(), get_that(), change_these(), and change_those().