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:

  1. 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.

  2. Walk the class’s method resolution order (MRO) in sequence. For each package in the order, look for a sub named foo.

  3. The first match wins. If no match is found in any class on the MRO, walk again looking for AUTOLOAD.

  4. If no AUTOLOAD is found either, throw Can'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 to D, B, A, C). A method on C that overrides A is shadowed by A because A is reached through B first.

  • 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:

  • isa returns false (not an error) when given a non-object. 42 isa Foo is false. undef isa Foo is false.

  • isa respects the live MRO at the moment of the call. If code pushes onto @ISA at runtime, isa sees it. (This is another reason pperl rejects @ISA manipulation for class-declared classes — the guarantees would become muddy.)

  • Prefer the isa operator over UNIVERSAL::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 method error.

  • Dispatching to a method by name from a string held in a variable — more readable than the $obj->$name form when the method might not exist.

Non-uses:

  • Do not use can to decide whether an object implements a conceptual role. A class can define a save method 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 :isa attribute only takes one target. If you need to mix behaviour from multiple places, see roles and delegation.

  • Runtime @ISA manipulation on a modern class. Undefined behaviour. Treat @ISA as read-only for any class declared with class.

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.