Understand Perl’s default inheritance model

Perl’s default inheritance mechanism is a bit weird, and it’s not something that any language designer would want to repeat. However, it is what it is and by knowing its quirks you should have any easier time tracking down inheritance problems.

Before you go further in this Item, though, you have to sit through this safety announcement, which we are going to announce loudly with an over-amped PA system: DON’T USE MULTIPLE INHERITANCE IF YOU CAN DO IT ANY OTHER WAY. Often, multiple inheritance is a crutch for either a bad design or a missing language feature. It also kills kittens. Try a different relationship or check out roles or traits to see if those might work for you. That’s the short story.

Now, having been warned, imagine this inheritance structure where you have two branches:

The idea of inheritance is that the lower classes don’t have to redo all of the work of the higher classes. The child classes only have to implement the parts that are different from the parent classes. If the child class doesn’t need to change a method, it doesn’t need to declare it at all. Through inheritance, you’ll eventually find the place where you defined the method and use that.

In Perl, that inheritance structure translates roughly to a series of packages that each define their upward connection:

use 5.014;

package A { ... }
package B { our @ISA = 'A'; ... }

package X { ... }
package Y { our @ISA = 'X'; ... }

package F { our @ISA = qw(B Y) }

You use the @ISA special variable here. You could use the parent pragma with -norequire since your classes are in their own files, but that’s a lot of work to just do the simple @ISA assignment.

Now, you call a method on an object from F. How does Perl find the class that implements the method? That is, what is Perl’s method resolution order? The method could be in F or in any of its parent classes? perl does a left-first, depth-first search. It looks in the order (F, B, A, Y, X). And perl uses the first implementation it finds.

UNIVERSAL

There’s more. The correct way to state this situation sounds overly simple. Perl searches through the classes you list. After Perl tries all of the subclasses you listed, in all of the branches of inheritance, it moves on to UNIVERSAL, as if you had said this:

package F { use parent qw(B Y UNIVERSAL) }

After Perl searches through the classes (and their parent classes) that you have explicitly defined and still doesn’t find the method, it tries UNIVERISAL. This is different from other systems where every class inherits from something like Object such that the ultimate parent class sits at the top.

Now, that’s a bit weird, you should think, because each of the parent classes should also have this behavior, right? In effect, it only applies to the class that you started with. To say that in another way, only the original class appears to inherit from UNIVERSAL. Curiously, this isn’t stated succinctly anywhere in the core perl documentation. It’s only clear in Chapter 12 in Programming Perl.

AUTOLOAD

There’s even more. If perl does not find the method in UNIVERSAL, it starts all over in the original class looking for the special method name AUTOLOAD, which we aren’t going to explain in this Item. The order turns out to be this:

  1. F::some_method
  2. B::some_method
  3. A::some_method
  4. Y::some_method
  5. X::some_method
  6. UNIVERSAL::some_method
  7. F::AUTOLOAD
  8. B::AUTOLOAD
  9. A::AUTOLOAD
  10. Y::AUTOLOAD
  11. X::AUTOLOAD
  12. UNIVERSAL::AUTOLOAD

Now, here’s the trick. Once perl finds one of those methods, including AUTOLOAD, it stops looking. There’s no way for an AUTOLOAD to defer itself so perl can keep searching. If the AUTOLOAD doesn’t want to handle it, you have to jump through black magic hoops to keep the process going.

Things to remember

  • perl searches through @ISA left first and depth first.
  • After going through @ISA, perl tries UNIVERSAL for the original class.
  • Failing UNIVERSAL, perl does another left first and depth first search looking for AUTOLOAD.