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 A
and B
could inherit more packages...
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
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
.