Search code examples
perlmethodsconstructormultiple-inheritance

Perl multiple inheritance, inheriting the same method name twice: How to use the right method correctly?


Well, I shouldn't use Perl using multiple inheritance, but I did. Consider the following code sketch ($self denotes the current object):

package A;
sub f { ... }
sub _init { ...; $self->f; ... }
sub new { ... $self->_init ... }

package B;
sub _init { ... }
sub new { ... $self->_init ... }

package C;
sub f { ... }
our @ISA = qw(A B);
sub new
{
    my $class = $_[0];
    my $self = ...;
    bless $self, $class;
    ??? call A:_init using $self, call B:_init using $self, setup C's attributes
}

The challenge for C->new is how to integrate the constructors from A and B (assuming _init does all the setup except blessing and allocating the object). Using $self->SUPER::_init could use only one of the two inherited methods, and it's not very obvious which (probably from the package listed in @ISA first).

The other thing is how to make sure that A::_init uses dynamic binding for f, i.e.: use C::f? Specifically using A::_init($self) does not. Specifically C could also have its own _init, and even Aand B could inherit more packages...

A compilable example

Here is a code sample that does compile, and it demonstrates what should happen (the commented statements are from https://stackoverflow.com/a/76786760/6607497 already):

#!/usr/bin/perl
require 5.018_000;
use strict;
use warnings;

package P1;

sub f($)
{
    $_[0]->[0] = 1;
}

sub _init($)
{
    $_[0]->f();
}

sub new($)
{
    my $class = $_[0];
    my $self = [];

    $#$self = 0;
    bless $self, $class;
    $self->_init();
    return $self;
}

package P2;

sub _init($)
{
    $_[0]->[0] = 2;
}

sub new($)
{
    my $class = $_[0];
    my $self = [];

    $#$self = 0;
    bless $self, $class;
    _self->_init();
    return $self;
}

package P3;

our @ISA = qw(P1 P2);

BEGIN {
    use Exporter qw(import);
}

sub f($)
{
    $_[0]->[1] = 3;
}

sub _init($)
{
    $_[0]->[2] = 4;
}

sub new($)
{
    my $class = $_[0];
    my $self = [];

    $#$self = 2;
    bless $self, $class;
    #$self->P1::_init();
    #$self->P2::_init();
    $self->_init();
    return $self;
}

package min;

my $o = P3->new();
$DB::single = 1;
print $o, "\n"; # prevent optimizing $o away too early

So while P1::f sets $self->[0] = 1, P3::f sets $self->[1] = 3, so P1's _init, when called from a P3 object, should not set $self->[0] = 1, but set $self->[1] = 3.

So the result should be:

  DB<1> x $o
0  P3=ARRAY(0xbd27e8)
   0  2
   1  3
   2  4

Solution

  • The child class can do this:

    package C;
    
    our @ISA = qw(A B);
    
    sub new {
        my $class = $_[0];
        my $self = ...;
        bless $self, $class;
        $self->A::_init();
        $self->B::_init();
        return $self;
    }
    

    If C had its own _init method, I'd write it like this:

    package C;
    
    our @ISA = qw(A B);
    
    sub new {
        my $class = $_[0];
        my $self = ...;
        bless $self, $class;
        $self->_init;
        return $self;
    }
    
    sub _init {
        my $self = shift;
        $self->A::_init();
        $self->B::_init();
        ...; # more initialization
    }
    

    One of your concerns seems to be making sure that $object->f uses dynamic binding. If you use the arrow operator (->) to call a method it will always use dynamic binding.

    So if you called A::_init($object) (static binding) but within A::_init a $self->f call happens, that call will use dynamic binding.

    So in my examples, why do I use $self->A::_init() instead of A::_init($self)? Because the former syntax uses dynamic binding — it just starts the method resolution at "A" instead of starting at "C".

    A more convoluted example:

    package Grandparent {
      use Class::Tiny;  # provides `new`
    
      sub init {
        my $self = shift;
        print "From Grandparent\n";
      }
    }
    
    package Parent1 {
      use parent -norequire, 'Grandparent';  # provides `@ISA`
    }
    
    package Parent2 {
      use Class::Tiny;
    
      sub init {
        my $self = shift;
        print "From Parent2\n";
      }
    }
    
    package Child {
      use parent -norequire, 'Parent1', 'Parent2';
    
      sub init {
        my $self = shift;
        $self->Parent1::init();
        $self->Parent2::init();
        print "From Child\n";
      }
    }
    
    my $eg = Child->new;
    $eg->init;
    

    Note in this example, Child inherits from two parent classes. Its init method calls the init method via each of its parents, including the Parent1 class which does not define an init method of its own but inherits it from Grandparent.