How to run _init for all ancestor classes in diamond inheritance

A

Amir Karger

I've got a simple diamond class hierarchy (code below). In order to
bore people, I've chosen to use Vehicle as the base class.
SolarVehicle and WheeledVehicle inherit from Vehicle, and SolarCar
inherits from both of those child classes. (But you could have a
SolarSeaPlane that's a SolarVehicle and not a WheeledVehicle.)

I want to specify a new and an _init for the base class.

new just blesses {} and calls _init.

_init should call _init for every parent class, so that you initialize
all the attributes of the object, including attributes of the parent
classes along with this class.

So how do I write Vehicle::_init such that SolarCar::_init will
inherit it and call Vehicle::_init, SolarVehicle::_init, and
WheeledVehicle::_init? (Writing SolarCar::_init so that it overloads
Vehicle::_init and calls the parent class _init's separate would be
unelegant, it seems to me.)

I've written code below that does everything except the step of
getting the base classes. I feel like my problem here isn't that Perl
can't do it, but just a syntax problem. I really just want a "foreach
(@ISA)" - but how do I write an inheritable Vehicle::_init and refer
to @ISA such that the @ISA of the package in which _init is currently
running is used, instead of always @Vehicle::ISA or @{ref($self) .
"::ISA"}.

I don't *think* this problem is due to diamond inheritance per se as
much as multiple inheritance in general. Also, I have a feeling Damian
Conway's Objects book answers this but I no longer have it. Long
story.

HELP!

-Amir Karger
(e-mail address removed)

=cut

##################
package Vehicle;
@Vehicle::ISA = ();

sub new {
my ($class, %args) = @_;
print "new $class\n";
my $self = {};
bless $self, $class;
$self->_init(%args);
}

sub _init {
my ($self, %args) = @_;
my $class = ref $self or die "no class for $self\n";
print "_init for class $class.";
# Note: can't use $class . "::ISA", because $class will
# always be the class of the original object.
my @bases = EVERYTHING IN THIS CLASS' @ISA;
foreach my $base (@bases) {
my $c = $base . "::_init";
$self->$c(%args);
}
$self->_class_init(%args);
}

sub _class_init {
my ($self, %args) = @_;
$self->{"name"} = "Default vehicle name";
}

package WheeledVehicle;
@WheeledVehicle::ISA = qw(Vehicle);

sub _class_init {
my ($self, %args) = @_;
$self->{"wheels"} = 4;
}

package SolarVehicle;
@SolarVehicle::ISA = qw(Vehicle);

sub _class_init {
my ($self, %args) = @_;
$self->{"panels"} = 2;
}

package SolarCar;
@SolarCar::ISA = qw(WheeledVehicle SolarVehicle);

sub _class_init {
my ($self, %args) = @_;
$self->{"panels"} = 2;
}
#######################
 
T

Tassilo v. Parseval

Also sprach Amir Karger:
I've got a simple diamond class hierarchy (code below). In order to
bore people, I've chosen to use Vehicle as the base class.
SolarVehicle and WheeledVehicle inherit from Vehicle, and SolarCar
inherits from both of those child classes. (But you could have a
SolarSeaPlane that's a SolarVehicle and not a WheeledVehicle.)

I want to specify a new and an _init for the base class.

new just blesses {} and calls _init.

_init should call _init for every parent class, so that you initialize
all the attributes of the object, including attributes of the parent
classes along with this class.

So how do I write Vehicle::_init such that SolarCar::_init will
inherit it and call Vehicle::_init, SolarVehicle::_init, and
WheeledVehicle::_init? (Writing SolarCar::_init so that it overloads
Vehicle::_init and calls the parent class _init's separate would be
unelegant, it seems to me.)

I don't agree here. Vehicle is the most generic of all your classes and
its _init Method should really only arrange for those things that every
vehicle has. Furthermore, Vehicle shouldn't have to bother whether it is
used as a superclass or not. Ideally, it doesn't even realize it.
I've written code below that does everything except the step of
getting the base classes. I feel like my problem here isn't that Perl
can't do it, but just a syntax problem. I really just want a "foreach
(@ISA)" - but how do I write an inheritable Vehicle::_init and refer
to @ISA such that the @ISA of the package in which _init is currently
running is used, instead of always @Vehicle::ISA or @{ref($self) .
"::ISA"}.

I don't *think* this problem is due to diamond inheritance per se as
much as multiple inheritance in general. Also, I have a feeling Damian
Conway's Objects book answers this but I no longer have it. Long
story.

HELP!

-Amir Karger
(e-mail address removed)

=cut

##################
package Vehicle;
@Vehicle::ISA = ();

sub new {
my ($class, %args) = @_;
print "new $class\n";
my $self = {};
bless $self, $class;
$self->_init(%args);
}

sub _init {
my ($self, %args) = @_;
my $class = ref $self or die "no class for $self\n";
print "_init for class $class.";
# Note: can't use $class . "::ISA", because $class will
# always be the class of the original object.
my @bases = EVERYTHING IN THIS CLASS' @ISA;
foreach my $base (@bases) {
my $c = $base . "::_init";
$self->$c(%args);
}
$self->_class_init(%args);
}

So trim this down to the minimum of what is required for a plain
Vehicle. Would become:

sub _init {
my ($self, %args) = @_;
$self->{name} = "Default vehicle name";
}

Your subclasses probably want to call the superclass' _init as well, I
assume. So they become:
package WheeledVehicle;
@WheeledVehicle::ISA = qw(Vehicle);

sub _class_init {
my ($self, %args) = @_;
$self->{"wheels"} = 4;
}

sub _init {
my ($self, %args) = @_;
$self->SUPER::_init;
$self->{wheels} = 4;
}
package SolarVehicle;
@SolarVehicle::ISA = qw(Vehicle);

sub _class_init {
my ($self, %args) = @_;
$self->{"panels"} = 2;
}

sub _init {
my ($self, %args) = @_;
$self->SUPER::_init;
$self->{panels} = 2;
}
package SolarCar;
@SolarCar::ISA = qw(WheeledVehicle SolarVehicle);

sub _class_init {
my ($self, %args) = @_;
$self->{"panels"} = 2;
}

This class has more than one ancestor:

sub _class_init {
my ($self, %args) = @_;
for (@SolarCar::ISA) {
my $init = $_->can("_init")
and $self->$init;
}
$self->{panels} = 2;
}

You can use this foreach loop in every _init method actually since it
checks whether each class in @ISA has (either through inheritance or by
providing it) an _init method.

The only problem with this is that Vehicle's _init method is now called
twice because both WheelVehicle::_init and SolarVehicle::_init will call
it.
#######################

Tassilo
 
D

Darin McBride

Amir said:
I've got a simple diamond class hierarchy (code below). In order to
bore people, I've chosen to use Vehicle as the base class.
SolarVehicle and WheeledVehicle inherit from Vehicle, and SolarCar
inherits from both of those child classes. (But you could have a
SolarSeaPlane that's a SolarVehicle and not a WheeledVehicle.)

I want to specify a new and an _init for the base class.

new just blesses {} and calls _init.

_init should call _init for every parent class, so that you initialize
all the attributes of the object, including attributes of the parent
classes along with this class.

Easiest way is to use NEXT (on CPAN) the same way you would use SUPER,
except that NEXT follows the ISA chain the way you want.

If you can't use NEXT directly, you may be able to steal some ideas
from it... which is what I did.

sub _init
{
my $class = shift;
my $orig_class = shift;

no strict 'refs'; # there be dragons here
# do ancestors first, allowing subclasses to "override" changes.
foreach my $super (@{"${class}::ISA"})
{
$super->_init();
}

if (
*{"${class}::class_init"}{CODE} and
ref *{"${class}::class_init"}{CODE} eq 'CODE' # should always be
true?
)
{
*{"${class}::initialise_parser"}{CODE}->();
}
}

There is probably a nicer way of saying some of this, but that's
basically the idea. Note that this doesn't take care of the diamond
shape where it may call the top-level class multiple times.
 
T

Tassilo v. Parseval

Also sprach Amir Karger:
I've got a simple diamond class hierarchy (code below). In order to
bore people, I've chosen to use Vehicle as the base class.
SolarVehicle and WheeledVehicle inherit from Vehicle, and SolarCar
inherits from both of those child classes. (But you could have a
SolarSeaPlane that's a SolarVehicle and not a WheeledVehicle.)

I want to specify a new and an _init for the base class.

new just blesses {} and calls _init.

_init should call _init for every parent class, so that you initialize
all the attributes of the object, including attributes of the parent
classes along with this class.

So how do I write Vehicle::_init such that SolarCar::_init will
inherit it and call Vehicle::_init, SolarVehicle::_init, and
WheeledVehicle::_init? (Writing SolarCar::_init so that it overloads
Vehicle::_init and calls the parent class _init's separate would be
unelegant, it seems to me.)

I don't agree here. Vehicle is the most generic of all your classes and
its _init Method should really only arrange for those things that every
vehicle has. Furthermore, Vehicle shouldn't have to bother whether it is
used as a superclass or not. Ideally, it doesn't even realize it.
I've written code below that does everything except the step of
getting the base classes. I feel like my problem here isn't that Perl
can't do it, but just a syntax problem. I really just want a "foreach
(@ISA)" - but how do I write an inheritable Vehicle::_init and refer
to @ISA such that the @ISA of the package in which _init is currently
running is used, instead of always @Vehicle::ISA or @{ref($self) .
"::ISA"}.

I don't *think* this problem is due to diamond inheritance per se as
much as multiple inheritance in general. Also, I have a feeling Damian
Conway's Objects book answers this but I no longer have it. Long
story.

HELP!

-Amir Karger
(e-mail address removed)

=cut

##################
package Vehicle;
@Vehicle::ISA = ();

sub new {
my ($class, %args) = @_;
print "new $class\n";
my $self = {};
bless $self, $class;
$self->_init(%args);
}

sub _init {
my ($self, %args) = @_;
my $class = ref $self or die "no class for $self\n";
print "_init for class $class.";
# Note: can't use $class . "::ISA", because $class will
# always be the class of the original object.
my @bases = EVERYTHING IN THIS CLASS' @ISA;
foreach my $base (@bases) {
my $c = $base . "::_init";
$self->$c(%args);
}
$self->_class_init(%args);
}

So trim this down to the minimum of what is required for a plain
Vehicle. Would become:

sub _init {
my ($self, %args) = @_;
$self->{name} = "Default vehicle name";
}

Your subclasses probably want to call the superclass' _init as well, I
assume. So they become:
package WheeledVehicle;
@WheeledVehicle::ISA = qw(Vehicle);

sub _class_init {
my ($self, %args) = @_;
$self->{"wheels"} = 4;
}

sub _init {
my ($self, %args) = @_;
$self->SUPER::_init;
$self->{wheels} = 4;
}
package SolarVehicle;
@SolarVehicle::ISA = qw(Vehicle);

sub _class_init {
my ($self, %args) = @_;
$self->{"panels"} = 2;
}

sub _init {
my ($self, %args) = @_;
$self->SUPER::_init;
$self->{panels} = 2;
}
package SolarCar;
@SolarCar::ISA = qw(WheeledVehicle SolarVehicle);

sub _class_init {
my ($self, %args) = @_;
$self->{"panels"} = 2;
}

This class has more than one ancestor:

sub _class_init {
my ($self, %args) = @_;
for (@SolarCar::ISA) {
my $init = $_->can("_init")
and $self->$init;
}
$self->{panels} = 2;
}

You can use this foreach loop in every _init method actually since it
checks whether each class in @ISA has (either through inheritance or by
providing it) an _init method.

The only problem with this is that Vehicle's _init method is now called
twice because both WheelVehicle::_init and SolarVehicle::_init will call
it.
#######################

Tassilo
 
T

Tassilo v. Parseval

Also sprach Amir Karger:
I've got a simple diamond class hierarchy (code below). In order to
bore people, I've chosen to use Vehicle as the base class.
SolarVehicle and WheeledVehicle inherit from Vehicle, and SolarCar
inherits from both of those child classes. (But you could have a
SolarSeaPlane that's a SolarVehicle and not a WheeledVehicle.)

I want to specify a new and an _init for the base class.

new just blesses {} and calls _init.

_init should call _init for every parent class, so that you initialize
all the attributes of the object, including attributes of the parent
classes along with this class.

So how do I write Vehicle::_init such that SolarCar::_init will
inherit it and call Vehicle::_init, SolarVehicle::_init, and
WheeledVehicle::_init? (Writing SolarCar::_init so that it overloads
Vehicle::_init and calls the parent class _init's separate would be
unelegant, it seems to me.)

I don't agree here. Vehicle is the most generic of all your classes and
its _init Method should really only arrange for those things that every
vehicle has. Furthermore, Vehicle shouldn't have to bother whether it is
used as a superclass or not. Ideally, it doesn't even realize it.
I've written code below that does everything except the step of
getting the base classes. I feel like my problem here isn't that Perl
can't do it, but just a syntax problem. I really just want a "foreach
(@ISA)" - but how do I write an inheritable Vehicle::_init and refer
to @ISA such that the @ISA of the package in which _init is currently
running is used, instead of always @Vehicle::ISA or @{ref($self) .
"::ISA"}.

I don't *think* this problem is due to diamond inheritance per se as
much as multiple inheritance in general. Also, I have a feeling Damian
Conway's Objects book answers this but I no longer have it. Long
story.

HELP!

-Amir Karger
(e-mail address removed)

=cut

##################
package Vehicle;
@Vehicle::ISA = ();

sub new {
my ($class, %args) = @_;
print "new $class\n";
my $self = {};
bless $self, $class;
$self->_init(%args);
}

sub _init {
my ($self, %args) = @_;
my $class = ref $self or die "no class for $self\n";
print "_init for class $class.";
# Note: can't use $class . "::ISA", because $class will
# always be the class of the original object.
my @bases = EVERYTHING IN THIS CLASS' @ISA;
foreach my $base (@bases) {
my $c = $base . "::_init";
$self->$c(%args);
}
$self->_class_init(%args);
}

So trim this down to the minimum of what is required for a plain
Vehicle. Would become:

sub _init {
my ($self, %args) = @_;
$self->{name} = "Default vehicle name";
}

Your subclasses probably want to call the superclass' _init as well, I
assume. So they become:
package WheeledVehicle;
@WheeledVehicle::ISA = qw(Vehicle);

sub _class_init {
my ($self, %args) = @_;
$self->{"wheels"} = 4;
}

sub _init {
my ($self, %args) = @_;
$self->SUPER::_init;
$self->{wheels} = 4;
}
package SolarVehicle;
@SolarVehicle::ISA = qw(Vehicle);

sub _class_init {
my ($self, %args) = @_;
$self->{"panels"} = 2;
}

sub _init {
my ($self, %args) = @_;
$self->SUPER::_init;
$self->{panels} = 2;
}
package SolarCar;
@SolarCar::ISA = qw(WheeledVehicle SolarVehicle);

sub _class_init {
my ($self, %args) = @_;
$self->{"panels"} = 2;
}

This class has more than one ancestor:

sub _class_init {
my ($self, %args) = @_;
for (@SolarCar::ISA) {
my $init = $_->can("_init")
and $self->$init;
}
$self->{panels} = 2;
}

You can use this foreach loop in every _init method actually since it
checks whether each class in @ISA has (either through inheritance or by
providing it) an _init method.

The only problem with this is that Vehicle's _init method is now called
twice because both WheelVehicle::_init and SolarVehicle::_init will call
it.
#######################

Tassilo
 
U

Uri Guttman

TvP> sub _class_init {
TvP> my ($self, %args) = @_;
TvP> for (@SolarCar::ISA) {
TvP> my $init = $_->can("_init")
TvP> and $self->$init;

i would double check if that works. there is a well known problem with
refering to a my var in the statement that declares it. the second $init
refers to a previously existing $init and is not strict safe. i would
write that as:

if (my $init = $_->can("_init") ) {
$self->$init;
}

and that is my safe and even clearer IMO.

uri
 
T

Tassilo v. Parseval

Also sprach Uri Guttman:
TvP> sub _class_init {
TvP> my ($self, %args) = @_;
TvP> for (@SolarCar::ISA) {
TvP> my $init = $_->can("_init")
TvP> and $self->$init;

i would double check if that works. there is a well known problem with
refering to a my var in the statement that declares it. the second $init
refers to a previously existing $init and is not strict safe. i would
write that as:

if (my $init = $_->can("_init") ) {
$self->$init;
}

and that is my safe and even clearer IMO.

You are right, sorry. I was too concerned with condensing it into one
statement for no reason.

Tassilo
 
A

Amir Karger

Tassilo v. Parseval said:
Also sprach Amir Karger:


I don't agree here. Vehicle is the most generic of all your classes and
its _init Method should really only arrange for those things that every
vehicle has. Furthermore, Vehicle shouldn't have to bother whether it is
used as a superclass or not. Ideally, it doesn't even realize it.

This is obviously an OO theory question, and I am very weak on OO
theory.
How does the idea that a class should be easily inheritable interface
with the idea that a class shouldn't know it's being inherited from?
Are classes really not supposed to know they're inherited from? What
about abstract classes, which can't even instantiate objects?
Obviously they'll be inherited from!

I guess the point of your argument is that all code in the base class
should represent the base class' functionality, and a class shouldn't
just be a convenient dumping ground for functionality that happens to
be shared between all the subclasses of that class. To me, though,
this conflicts with the idea that I can write the code just once in
the base class, rather than writing a new _init in every subclass.
That makes it easier to inherit, avoids bugs in needlessly replicated
_init functions, and elegance-wise avoids redundant code. So the real
question is, is that True or False Laziness? I can see an argument
for each side. As it happens, in my application, I don't think
there'll ever be more than four classes. But what if I had thirty
classes; should I really put a new _init in each one?

Would you be happier if I did this:

package Vehicle;
# Just do stuff that applies to Vehicle AND its subclasses
sub _init {
shift->{"name"} = "default name";
}

package ClassThatInheritsFromVehicle;
@ISA = qw(Vehicle);
# Do stuff that applies only to subclasses
sub _init {
# put my Vehicle::_init here
}

and then have every subclass inherit from there instead? Now I'm
creating a class whose whole point is to foster good inheritance, so
it makes sense to put an inheritance-helping _init in there. Vehicle
now has only Vehicle-related functionality, and all the sub-Vehicle
classes just inherit from Vehicle. But is it really worth creating a
class like this just to allow inheritance? My sense of OO is that it's
OK if I centralize code for all the subclasses in a superclass, as
long as that code doesn't break the superclass. This may break OO
theory, but is it the kind of breaking that Perl approves of and
sometimes encourages?

I guess this problem is arising because I'm trying to force a non-Perl
OO model (essentially calling every ancestor class' new()) onto Perl.
Still, I would think it's the rule rather than the exception that
you'd want objects of the child classes to have the attributes of the
parent classes. (Perl 6 has Rules AND Exceptions :) And there are
probably many cases out there where there's more than one layer of
inheritance, and some multiple inheritance, such that you can't just
call $self->SUPER::init(). What have other folks used in this
situation?

As it happens, I realized I can solve this problem by passing $class
as the first argument to _init as I move up the ladder. Then I just
use
@{$class . "::ISA"}. Of course, this is pretty ugly. It's kind of
strange to me to think that if you call $foo->class::bar() then bar()
can't tell what class it was called with. caller() doesn't seem to
give me the right info either. So I'm still somewhat unsatisfied.
(Sounds like the kind of thing Perl6 would have an operator for,
though. How about <- to mean the class I was called with?)

But the rest of your email is probably the more important part.

[Generously offered code snipped]
The only problem with this is that Vehicle's _init method is now called
twice because both WheelVehicle::_init and SolarVehicle::_init will call
it.

This is a very minor problem which also existed in my code: I can put
a hash in the object that stores which classes' inits have been called
on the object so far, and return if $self->{"_init"}{$class}.

-Amir
 
M

Malte Ubl

Amir said:
I've got a simple diamond class hierarchy (code below). In order to
bore people, I've chosen to use Vehicle as the base class.
SolarVehicle and WheeledVehicle inherit from Vehicle, and SolarCar
inherits from both of those child classes. (But you could have a
SolarSeaPlane that's a SolarVehicle and not a WheeledVehicle.)

Everytime you have a SolarWheeledMountableWindowedScollableCyberVehicle
there is a simple answer:

USE DELEGATION

or more specific the decorator pattern.


bye
malte
 
T

Tassilo v. Parseval

Also sprach Amir Karger:
This is obviously an OO theory question, and I am very weak on OO
theory.
How does the idea that a class should be easily inheritable interface
with the idea that a class shouldn't know it's being inherited from?
Are classes really not supposed to know they're inherited from? What
about abstract classes, which can't even instantiate objects?
Obviously they'll be inherited from!

The problem is when superclasses contain code that is really just there
to satisfy the needs of a particular subclass. This makes deriving
another class harder since this new class might not have these
requirements.
I guess the point of your argument is that all code in the base class
should represent the base class' functionality, and a class shouldn't
just be a convenient dumping ground for functionality that happens to
be shared between all the subclasses of that class. To me, though,
this conflicts with the idea that I can write the code just once in
the base class, rather than writing a new _init in every subclass.

Common code should go into superclasses, that's right. What I didn't
like with your approach was that the class Vehicle would look down onto
its child classes and see from which classes those are derived by
looking at their @ISA. Consider you derive a new class from SolarVehicle
that - for some reason - only wants to call Vehicle's _init method and
not that of SolarVehicle. This would be impossible in an approach where
the class highest in the hierarchy decides which init-methods to call.
That makes it easier to inherit, avoids bugs in needlessly replicated
_init functions, and elegance-wise avoids redundant code. So the real
question is, is that True or False Laziness? I can see an argument
for each side. As it happens, in my application, I don't think
there'll ever be more than four classes. But what if I had thirty
classes; should I really put a new _init in each one?

Would you be happier if I did this:

package Vehicle;
# Just do stuff that applies to Vehicle AND its subclasses
sub _init {
shift->{"name"} = "default name";
}

package ClassThatInheritsFromVehicle;
@ISA = qw(Vehicle);
# Do stuff that applies only to subclasses
sub _init {
# put my Vehicle::_init here
}

and then have every subclass inherit from there instead? Now I'm
creating a class whose whole point is to foster good inheritance, so
it makes sense to put an inheritance-helping _init in there. Vehicle
now has only Vehicle-related functionality, and all the sub-Vehicle
classes just inherit from Vehicle. But is it really worth creating a
class like this just to allow inheritance? My sense of OO is that it's
OK if I centralize code for all the subclasses in a superclass, as
long as that code doesn't break the superclass. This may break OO
theory, but is it the kind of breaking that Perl approves of and
sometimes encourages?

It's probably best to put OO theory aside when programming Perl. The
fact that you can look at your child classes' @ISA is already something
that most other object-oriented classes don't allow and would consider a
severe violation against the principles of OO. Screw that, the purity of
ideas is never something Perl cared about a lot.
As it happens, I realized I can solve this problem by passing $class
as the first argument to _init as I move up the ladder. Then I just
use
@{$class . "::ISA"}. Of course, this is pretty ugly. It's kind of
strange to me to think that if you call $foo->class::bar() then bar()
can't tell what class it was called with. caller() doesn't seem to
give me the right info either. So I'm still somewhat unsatisfied.
(Sounds like the kind of thing Perl6 would have an operator for,
though. How about <- to mean the class I was called with?)

Are you sure that caller() wont help you? The first item of the list
returned by caller() is always the package from which the current
function was called. That should be enough.
This is a very minor problem which also existed in my code: I can put
a hash in the object that stores which classes' inits have been called
on the object so far, and return if $self->{"_init"}{$class}.

Sounds too complicated. Just make sure that the _init() classes only do
things that wont break when they are done twice.

Tassilo
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top