views:

192

answers:

3

I created a module Foo::Prototype with the global variables $A and $B. I want the package Foo::Bar that uses Foo::Prototype as a base to import the global variable $A and $B. I could not figure how to do that.

I understand that using global variables is not a good practice in general, but in this case I want to use them.

The code looks like this:

package Foo:Prototype;
my ($A, $B);
our @EXPORT = qw($A $B);

sub new {
    [...]
    $A = 1;
    $B = 2;
}

1;

package Foo:Bar;
use base Foo:Prototype qw($A $B);

sub test {
    print $A, "\n";
    print $B, "\n";
}

1;


# test.pl
Foo:Bar->new();
Foo:Bar->test();

Edit:

I want to make writing sub classes of Foo::Prototype as compact as possible for other people. Instead of having to write $self->{A}->foo(), I'd rather let people write $A->foo().

+4  A: 

Well, there are a few of issues:

  1. As brian points out, your problem can probably be solved better without using global variables. If you describe what you are trying to achieve rather than how, we may be able to provide better answers.

  2. If you are going to export stuff, you either need a sub import or you need to inherit from Exporter. See perldoc Exporter.

  3. It is not clear where you want the call to new to occur.

  4. As Greg points out in a comment below, variables declared with my at package scope cannot be exported. Therefore, I declared $A and $B using our.

Here is something that "works" but you are going to have to do some reading and thinking before deciding if this is the way you want to go.

T.pm:

package T;
use strict;
use warnings;

use base 'Exporter';

our ($A, $B);
our @EXPORT = qw($A $B);

sub new {
    $A = 1;
    $B = 2;
}

"EOF T.pm"

U.pm:

package U;

use strict;
use warnings;

use base 'T';
use T;

sub test {
    my $self = shift;
    print "$_\n" for $A, $B;
}

"EOF U.pm"

t.pl:

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

use U;

U->new;
U->test;

C:\Temp> t.pl
1 
2
Sinan Ünür
Nice answer. You covered it implicitly, but I'd emphasize that Exporter will not export lexicals.
Greg Bacon
@gbacon Thank you.
Sinan Ünür
+3  A: 

The trick is to not have to export variables. That's a very poor way to program.

Maybe there's a better way to accomplish whatever you want to do. You just have to tell us why you're trying to do that.

brian d foy
+4  A: 

Based on your edit, $A and $B will be used to call methods on.

So, I assume that they are singleton objects stored as class data for the base class.

If you expose them as variables, they can be easily altered and all kinds of problems can occur.

Why not use an accessor?

package Foo::Proto;

my $A;
my $B;

sub A {
   return $A;
}

sub B {
   return $B;
}

package Foo::Child;
our @ISA= qw(Foo::Prototype);

sub test {
    my $self = shift;

    $self->A->blah();

    # Or if I am doing many things with A, and want to type less:
    my $A = $self->A;
    $A->blah();   
}

package Foo::Kid;
our @ISA= qw(Foo::Prototype);

# If you will never change $A in the prototype, you could do this:
my $A = __PACKAGE__->A;

sub test {
  $A->blah();
}

But all this seems like a lot of mucking about.

To solve this problem in my code I would use Moose, and then create a role to bring in A and B related methods.

my $m = Foo::Mooseling->new();
$m->test_A();
$m->test_B();


BEGIN {  # This is going to be $A, I needed something to call $A->foo on.
    package Thing1;  
    sub new  { bless {}, __PACKAGE__; }
    sub foo  { print __PACKAGE__."::foo()\n"; }
    sub blah { print __PACKAGE__."::blah()\n"; }
}
BEGIN {  # This is going to be B. It is not interesting either.
    package Thing2;
    sub new  { bless {}, __PACKAGE__; }
    sub bar  { print __PACKAGE__."::bar()\n"; }
    sub bluh { print __PACKAGE__."::bluh()\n"; }
}

# This is the interesting part:    
BEGIN {  # This ROLE will provide A and B methods to any objects that include it.
    package Foo::ProtoMoose;
    use Moose::Role;

    has 'A' => (
        is => 'ro',
        isa => 'Thing1',
        handles => [qw( foo blah )],  # Delegate calls to foo and blah for consuming object to this A.
        default => sub { Thing1->new(); }, # Create a Thing1 to be A.
    );

    has 'B' => (
        is => 'ro',
        isa => 'Thing2',
        handles => [qw( bar bluh )],       
        default => sub { Thing2->new(); },
    ); 
}

BEGIN {  # This method consumes the ProtoMoose Role.
    package Foo::Mooseling;
    use Moose;

    with 'Foo::ProtoMoose';

    sub test_A {
        my $class = shift;

        $class->foo;
        $class->blah;
    }

    sub test_B {
        my $class = shift;

        $class->bar;
        $class->bluh;
    }

}

If you want Thing1 and Thing2 to be singletons, use MooseX::Singleton.

daotoad
+1 Excellent intro to Moose.
Sinan Ünür