Inheritance and method resolution#
Method lookup in Perl is simple in the single-inheritance case and
genuinely subtle once diamonds or mixed class systems enter the
picture. This chapter covers the full story: the MRO algorithms,
SUPER::, the isa operator,
can, DOES, and the interaction between classical and modern
classes in one hierarchy.
The lookup algorithm#
When you write $obj->foo(@args), Perl does the following:
Determine the class. For an object, it is the class the referent was blessed into; for a class-method call, it is the invocant itself.
Walk the class’s method resolution order (MRO) in sequence. For each package in the order, look for a sub named
foo.The first match wins. If no match is found in any class on the MRO, walk again looking for
AUTOLOAD.If no
AUTOLOADis found either, throwCan't locate object method "foo" via package "Class".
The MRO is what the class’s @ISA chain linearises to. The
linearisation rule depends on which MRO is in effect.
The two MROs: dfs and c3#
Perl ships two MRO algorithms:
dfs— depth-first search of@ISA. The default. Walk each parent, and each parent’s parents, left-to-right, diving fully into each subtree before moving on.c3— the C3 linearisation used by Python, Raku, and many others. Guarantees monotonicity: a subclass never appears later than one of its ancestors in the lookup order.
For single inheritance the two are identical: the list walks straight up the chain.
For multiple inheritance they diverge. Consider the diamond:
A
/ \
B C
\ /
D
dfs:D, B, A, C, A(collapsed toD, B, A, C). A method onCthat overridesAis shadowed byAbecauseAis reached throughBfirst.c3:D, B, C, A.C’s override is visible.
C3 is almost always what you want under multiple inheritance. Opt in per-class:
use mro 'c3';
package Diamond::D;
our @ISA = ('Diamond::B', 'Diamond::C');
For classes declared with class,
multiple inheritance is not supported at all, so the MRO choice
is purely a linear walk and the default dfs is fine.
SUPER::#
$self->SUPER::foo(@args) calls foo in one of the parent
classes of the package the current method was compiled in.
That last phrase is the one everyone gets wrong.
package Animal;
sub speak { 'some sound' }
package Dog;
our @ISA = ('Animal');
sub speak { 'woof' }
package Puppy;
our @ISA = ('Dog');
sub speak {
my $self = shift;
return $self->SUPER::speak;
}
Puppy::speak calls SUPER::speak. Perl looks up from
Puppy (the compilation package), not from ref($self).
The search starts in Puppy’s @ISA, so it finds
Dog::speak and returns 'woof'.
This matters when methods are inherited and the child calls
SUPER:: from within. Because SUPER:: is anchored to the
compile-time package, a method inherited from Dog and invoked
on a Puppy object will still call Animal:: via its
SUPER::, not Dog. Usually that is the right behaviour; when
it is not, reach for explicit chain navigation with
next::method, covered below.
next::method — the MRO-aware SUPER#
mro::next::method (exposed as $self->next::method(@args))
walks the live MRO from the current method’s actual call
position, not from the compile-time package:
use mro 'c3';
sub speak {
my $self = shift;
return $self->next::method;
}
Under C3 with a diamond, next::method is what you want;
SUPER:: can skip a sibling branch because it only consults the
left-most ancestor.
The isa operator#
The isa operator tests class
membership without the footgun of UNIVERSAL::isa called as a
function:
if ($obj isa 'File::MP3') { ... }
if ($obj isa File::MP3) { ... } # bareword form
Both bless-based and class-declared
classes answer isa correctly, and subclass relationships flow
through :isa and @ISA identically.
Three rules worth remembering:
isareturns false (not an error) when given a non-object.42 isa Foois false.undef isa Foois false.isarespects the live MRO at the moment of the call. If code pushes onto@ISAat runtime,isasees it. (This is another reason pperl rejects@ISAmanipulation forclass-declared classes — the guarantees would become muddy.)Prefer the
isaoperator overUNIVERSAL::isa($obj, 'Class'). The operator short-circuits on non-objects instead of croaking; the function-call form is also defeated by overloaded objects.
can#
$obj->can('method') returns a code reference if the object has
a method by that name, undef otherwise:
if (my $code = $obj->can('save')) {
$code->($obj, @args);
}
Uses:
Feature-detection before calling: avoids the
Can't locate object methoderror.Dispatching to a method by name from a string held in a variable — more readable than the
$obj->$nameform when the method might not exist.
Non-uses:
Do not use
canto decide whether an object implements a conceptual role. A class can define asavemethod that does something completely unrelated to persistence. For role membership, see roles and delegation.
DOES#
$obj->DOES('RoleOrClass') is the role-aware counterpart to
isa. By default DOES delegates to isa, so for classes
without explicit role machinery the two behave the same. Role
systems (classical Moose, Moo, and the future core role
feature) override DOES to also return true for roles the class
consumes.
if ($obj->DOES('Printable')) { ... }
In pperl, DOES is the right method to check when you want to
ask “does this object implement the contract?” rather than
“does this object inherit from this class?”
Mixed classical / modern hierarchies#
class-declared and bless-based classes interoperate in both
directions, subject to a handful of rules.
A modern class inheriting from a classical class#
package Shape; # classical
sub new {
my ($class, %args) = @_;
return bless { colour => $args{colour} // 'black' }, $class;
}
sub colour { $_[0]{colour} }
use feature 'class';
class Circle :isa(Shape) { # modern inherits classical
field $radius :param :reader;
method area { 3.14159265 * $radius ** 2 }
}
This works. The modern class’s generated constructor delegates to
the parent’s new, and methods inherited from the classical
parent appear on the modern child. The parent’s hash-slot state
lives alongside the child’s field storage.
Caveat: the modern child cannot reach into the parent’s hash.
$self->{colour} is not legal inside a method of Circle; you
must call $self->colour.
A classical class inheriting from a modern class#
use feature 'class';
class Animal {
field $name :param :reader;
method speak { 'some sound' }
}
package Dog; # classical inherits modern
our @ISA = ('Animal');
sub new {
my ($class, %args) = @_;
return $class->SUPER::new(%args);
}
sub speak { 'woof' }
This also works — the modern parent’s generated constructor is
inherited, and SUPER::new resolves to it. The classical child
must not try to mutate the parent’s fields directly; fields are
private to the class that declared them.
What fails in mixed hierarchies#
Reaching
$self->{fieldname}across the boundary. Modern fields are not hash slots, and classical hash slots are not visible as fields. Always cross the boundary through a method.Multiple inheritance into a modern class. The
:isaattribute only takes one target. If you need to mix behaviour from multiple places, see roles and delegation.Runtime
@ISAmanipulation on a modern class. Undefined behaviour. Treat@ISAas read-only for any class declared withclass.
Why multiple inheritance usually hurts#
Every language with multiple inheritance eventually discovers the same pathologies. Method lookup becomes order-dependent; diamond problems force you to think about linearisation; the “I inherited twice from the same root” case tangles initialisation. Roles — composition of behaviour without an inheritance relationship — solve the underlying sharing problem without the pathologies.
In pperl the class feature
forbids multiple inheritance deliberately. Classical code that
uses it should be migrating towards a role model; the roles
chapter is the next stop.
Further reading#
class— full syntax of modern class declarations including:isa.bless— the primitive under classical inheritance.ref— reports a blessed reference’s class name.isa— the preferred class- membership operator.Roles and delegation — the composition- based alternative to multiple inheritance.
Migration — walking an existing hierarchy from classical to modern.