wawawawa
wawawawa

Reputation: 431

Inheriting Constants with inline packages

OK. I have a problem trying to inherit constants set in a parent class for any of the child classes.

#!/usr/bin/perl
use strict;
use warnings;

package Car;
use Exporter qw( import );
our @EXPORT_OK = ( 'WHEELS', 'WINGS' );

use constant WHEELS => 4;
use constant WINGS  => 0;

sub new {
    my ( $class, %args ) = @_;
    my $self = {
        doors  => $args{doors},
        colour => $args{colour},
        wheels => WHEELS,
        wings  => WINGS,
    };
    bless $self, $class;
    return $self;
}

package Car::Sports;
use base qw( Car );

sub new {
    my ( $class, %args ) = @_;
    my $self = {
        doors  => $args{doors},
        engine => $args{engine},
        wheels => WHEELS,
        wings  => WINGS,
    };
    bless $self, $class;
    return $self;
}

package main;
my $obj = Car->new( doors => 4, colour => "red" );
print Dumper $obj;

my $obj2 = Car::Sports->new( doors => 5, engine => "V8" );

print Dumper $obj2;
__END__

The error is:

Bareword "WHEELS" not allowed while "strict subs" in use at ./t.pl line 30.
Bareword "WINGS" not allowed while "strict subs" in use at ./t.pl line 30.
Execution of ./t.pl aborted due to compilation errors.

Now, I haven't come here to post without doing some research. I understand that one option would be to use Car qw( WHEELS WINGS) in Car::Sports. However, if I do that I get the following error, because the classes are all inline in the same file:

Can't locate Car.pm in @INC 

For a variety of reasons, I need to keep my packages in one file. Is there a way around this? As constants are basically just subs, why do I have to import them when the same would not be true for a normal method?

Finally, I also know I can do this:

package Car::Sports;
use base qw( Car );

sub new {
    my ( $class, %args ) = @_;
    my $self = {
        doors  => $args{doors},
        engine => $args{engine},
        wheels => Car::WHEELS,
        wings  => Car::WINGS,
    };
    bless $self, $class;
    return $self;
}

And it's fine... But I have a number of classes and want to make the inheritance of constants more generic that having to name the parent class explicitly (and sometimes it's not just the parent class, but the grandparent).

Many thanks in advance for any pointers!

Cheers

Upvotes: 5

Views: 2506

Answers (4)

darch
darch

Reputation: 4311

Generally, exposing that something is a constant to any package other than the one defining it is actually a bad idea. This argues, among other things, against using unusual forms when referring to values that happen to be constant in other areas of your code.

The constant module actually supports an invocation form that hides the fact that we're talking about constants, inasmuch as calling constants as class methods works just fine:

package Car;
use constant default_wheel_count => 4;

package Car::Sports;

sub new {
    my ($class) = @_;

    return bless {
        wheels => $class->default_wheel_count,
    } => $class;
}

That's how one actually inherits constants, but it's still probably the wrong approach. Eliminating the copypasta by only using the constants from the classes that implement construction of those attributes is the actual right thing to do.

Upvotes: 0

ikegami
ikegami

Reputation: 386331

Alternative, you could do exactly what use does:

BEGIN {
    package Car;
    use Exporter qw( import );
    @EXPORT_OK = qw( WHEELS );

    ...

    $INC{'Car.pm'} = 1;
}

BEGIN {
    package Car::Sports;

    use Car qw( WHEELS );
    @ISA = 'Car';

    ...

    $INC{'Car/Sports.pm'} = 1;
}

Upvotes: 3

mob
mob

Reputation: 118635

One workaround is to include the line

package Car::Sports;
use base qw( Car );
Car->import(qw(WHEELS WINGS));

AND use the sigils in the Car::Sports constructor:

...
wheels => &WHEELS,
wings  => &WINGS,
...

Your Car class isn't defining its @EXPORTS_OK list until run-time. The sigils are required because the Car::Sports constructor is parsed at compile-time, and the compiler doesn't know there should be WHEELS and WINGS symbols in the Car::Sports namespace.


The only way to avoid the sigils is to define Car's exports at compile-time:

package Car;
our @EXPORT_OK;
BEGIN {@EXPORT_OK = qw(WHEELS WINGS)} # set at compile not run time
...

package Car::Sports;
use base qw(Car);
BEGIN {Car->import('WHEELS','WINGS')} # import before c'tor is parsed

You could also avoid these machinations by defining the Car class in its own Car.pm file. Then you would just say

use Car qw(WHEELS WINGS);

and everything in the Car.pm file would be parsed at compile time, AND the Exporter::import method (triggered by a call to Car::import) would automatically get run and import the desired symbols to your current namespace.

Upvotes: 6

Marco De Lellis
Marco De Lellis

Reputation: 1189

May this change suit your needs?

    [...]
    wheels => $class->SUPER::WHEELS,
    wings  => $class->SUPER::WINGS,
    [...]

Using Data::Dumper you get:

$VAR1 = bless( {
             'wings' => 0,
             'colour' => 'red',
             'doors' => 4,
             'wheels' => 4
           }, 'Car' );
$VAR1 = bless( {
             'wings' => 0,
             'engine' => 'V8',
             'doors' => 5,
             'wheels' => 4
           }, 'Car::Sports' );

Upvotes: 3

Related Questions