So far as multi methods go, it's not really to do with being in the same module or file at all. Consider these classes:
class Base {
proto method m(|) { * }
multi method m() { 1 }
}
class Derived is Base {
multi method m() { 2 }
}
Whenever we compose a class that contains multi methods, we need to attach them to a controlling proto. In the case of Base, this was explicitly written, so there's nothing to do other than to add the multi candidate to its candidate list. Had we not written a proto explicitly in Base, however, then one with an empty candidate list would have been generated for us, with the same end result.
The process I just described is a bit of a simplification of what really happens, however. The steps are:
- See if this class has a
proto already; if so, add the multi to it
- Otherwise, see if any base class has a
proto; if so, clone it (in tern cloning the candidate list) and add the multi to that.
- Otherwise, generate a new
proto.
And step 2 is really the answer to your question. If we do:
say "Base:";
.raku.say for Base.^lookup('m').candidates;
say "Derived:";
.raku.say for Derived.^lookup('m').candidates;
Then the output is:
Base:
multi method m (Base: *%_) { #`(Method|82762064) ... }
Derived:
multi method m (Base: ) { #`(Method|82762064) ... }
multi method m (Derived: ) { #`(Method|82762208) ... }
That is, the candidate list in Base has one entry, and the candidate list in Derived has the entry cloned from Base as well as a new one.
Pretty much everything follows this principle: derived classes reference their base class (and the roles they do), but base classes (and roles) don't know about their descendants.