Search code examples
multiple-inheritancerakudiamond-problem

How does Raku deal with the diamond problem (multiple inheritance)?


So it's no secret that Raku has multiple inheritance, so that got me wondering: "how does Raku deal with that in any reasonable manner?"

Some preliminary testing reveals that default behaviour is inherited from the first class in the inheritance list, that's fine, many other languages do it like that too

class A {
    has $.foo = 0;

    method speak {...}
}

class B is A {
    has $.foo = 1;

    method speak {
        say 'moo';
    }
}

class C is A {
    has $.foo = 2;

    method speak {
        say 'baa';
    }
}

class D is B is C {}

class E is C is B {}

say D.new.foo; # prints 1 (from B)
say E.new.foo; # prints 2 (from C)

But that got me wondering, what if I want D to use C's speak? Due to the inheritance order I get B's by default.

I understand that roles exist to solve this exact issue by facilitating a disambiguation mechanism, but suppose hypothetically I find myself in a situation where I don't have roles at my disposal (boss hates them, inherited a library that doesn't have them, pick your excuse) and really need to disambiguate the inherited classes.

What's the mechanism for dealing with that in Raku?


Solution

  • Generally you would need to provide a tie-breaker method in the class that has the (potential) ambiguity. Fortunately, you do not have to create separate methods, as you can call specific versions of methods in a call.

    So the answer to your question: Add a method speak to class D, and have that call the speak method from class C:

    class D {
        ...
        method speak { self.C::speak }
    }
    

    And for methods taking parameters, take a Capture of all parameters, and pass that on:

    class D {
        ...
        method foo(|c) { self.C::foo(|c) }
    }
    

    Note that the "c" in |c is just an identifier, it could be any identifier. But it is sorta customary, at least with Raku core developers, to just use |c, with the "c" standing for "capture".

    Now, this will cause some overhead, as you would have an extra level of indirection. Should this turn out to be a performance issue, there's some meta-programming you can do to alias the speak method in D to the speak method in C:

    class D {
        ...
        BEGIN D.^add_method("speak",C.^find_method("speak"));
    }
    

    This will, at compile time because of the BEGIN, add a Method object to the D class called speak that is defined by the speak method of class C. Since this is an alias, you don't have to worry about any parameters being passed.