Perl Class Data

Ah, I think I’ve just worked our what all the ours were being used for (see my previous post. I think they wanted class data. I googled for a few tutorials and docs and, unsurprisingly, it turns out that there are multiple ways of implementing class data in Perl. The fundamental problem is that Perl doesn’t really have Classes, it just has Packages that quack like a Class. This means it doesn’t have Class Variables, it has Package Variables or Lexical Variables.

Given that a Perl Class is just a Package, it’s not unreasonable to think package variables would make good class variables. So you might try something like this:

TestClass.pm

package TestClass;
$pkg_var = 100;

Of course, you’re probably using strict , which will complain if you try to use a package variable that doesn’t exist yet. For this to work under strict you have to either fully qualify the name, or use our:

TestClass.pm

use strict;
package TestClass;

#this doesn't work under strict
#$pkg_var=100;

#but this will
our $pkg_var = 100;

# as will this
$TestClass::pkg_var = 100;

But you’re likely to hit problems as soon as you start dealing with inheritance. Child classes don’t inherit their parent’s package variables (although there’s nothing stopping them accessing them directly with their fully qualified name), @ISA is searched for methods, not variables.

TestClass.pm

 
use strict;
use warnings;
package TestClass;
our $pkg_var = 100;
1;

TestClassChild.pm

 
use strict;
use warnings;
package TestClassChild;
use base 'TestClass';
1;

test.pl

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

my $tc = TestClass->new();
my $tcc = TestClassChild->new();

print $TestClass::pkg_var."\n";
# => 100

# Child doesn't "inherit" the package variable
print $TestClassChild::pkg_var."\n";
# =>

Child classes do inherit parent subs though, so you can write an accessor function to your class data:

TestClass.pm

use strict;
use warnings;
package TestClass;

our $pkg_var = 100;
sub pkg_var {
    my ($self, $arg) = @_;
    $pkg_var = $arg if defined $arg;
    return $pkg_var;
}
1;

TestClassChild.pm

use strict;
use warnings;
package TestClassChild;
use base 'TestClass';
1;

test.pl

#!/usr/bin/perl

use strict;
use warnings;
use TestClass;
use TestClassChild;

my $tc = TestClass->new();
my $tcc = TestClassChild->new();

print $tc->pkg_var()."\n";
#=> 100
print $tcc->pkg_var()."\n";
#=> 100

# but they are all still referring to the parent class, the child doesn't get its own copy
print $tcc->pkg_var(53)."\n";
#=> 53
print $tc->pkg_var()."\n";
#=> 53

If you want the child class to have it’s own class data, you can’t just give it its own package variable. If you do this:

TestClassChild.pm

use strict;
use warnings;
package TestClassChild;
use base 'TestClass';
our $pkg_var = 200;
1;

then test.pl still gives you:

print $tc->pkg_var()."\n";
#=>100
print $tcc->pkg_var()."\n";
#=>100

print $tcc->pkg_var(53)."\n";
#=>53
print $tc->pkg_var()."\n";
#=53

because the $pkg_var is being evaluated in the context of the sub definition, and that is in the parent class. To get what you want, you have to override the sub too:

TestClassChild.pm

use strict;
use warnings;
package TestClassChild;
use base 'TestClass';
our $pkg_var = 200;
sub pkg_var {
    my ($self, $arg) = @_;
    $pkg_var = $arg if defined $arg;
    return $pkg_var;
}
1;

and now test.pl works like you intended:


print $tc->pkg_var()."\n";
#=> 100
print $tcc->pkg_var()."\n";
#=>200

print $tcc->pkg_var(53)."\n";
#=>53
print $tc->pkg_var()."\n";
#=>100

The thing is, package variables do a really bad job of pretending to be class variables. As discussed earlier they’re basically just globals with namespaces. They don’t give you any data encapsulation. Your class data can be messed with by any code in your program, provided it knows the full qualified name. This is why Perl 5 has lexical variables. A file-scoped lexical variable and associated accessor can work just as well as the package variable above, only now the variable itself is file scoped and can’t be seen by things outside the class.

TestClass.pm

use strict;
use warnings;
package TestClass;

# A file-scoped lexical with an accessor 
my $my_var = 100;
sub my_var {
    my ($self, $arg) = @_;
    $my_var = $arg if defined $arg;
    return $my_var; 
    
} 
1;

Child classes will still be pointing at the parent class data unless they override the variables and the function

TestClassChild

use strict;
use warnings;
package TestClassChild;
use base 'TestClass';

my $my_var = 200;
sub my_var {
    my ($self, $arg) = @_;
    $my_var = $arg if defined $arg;
    return $my_var;
}
1;

and now in test.pl


print $tc->my_var()."\n";
#=> 100
print $tcc->my_var()."\n";
#=> 200

print $tcc->my_var(42)."\n";
#=> 42
print $tc->my_var()."\n";
#=> 100

So, the data is now hidden behind an accessor for code outside the class package. We can take this one step further and completely hide the data behind an accessor, even within the class by simply defining it in its own scope:

testClass.pm

use strict;
use warnings;
package TestClass;

# A block scoped variable with an accessor 
{ 
    my $closure_var = 100;
    sub closure_var {
	my ($self, $arg) = @_;
	$closure_var = $arg if defined $arg;
	return $closure_var; 
	
    } 
}

#and just to prove that it's hidden
use PadWalker qw(peek_my);
sub cant_see_closure_var { 
    my $current = peek_my(0);
    return "nope, I can't see it" unless $current->{'$closure_var'};   
} 

1;

and now in test.pl the accessor still works fine because it has closed over the variable, but other methods in the class don’t have direct access to it.

print $tc->closure_var()."\n";
#=>100
print $tc->closure_var(42)."\n";
#=>42
print $tc->cant_see_closure_var."\n";
#=>nope, I can't see it

Even Moose doesn’t have a built in solution for class data because there way in which is should behave in interitance, particularly multiple inheritance, is unclear. There’s a discussion on PerlMonks about the subject. Note in particular Moose author, Stevan Little’s comment. He says if he has to use class data, he does it with scoped variables and accessors (as shown above, I guess), but recommends taking a look at MooseX::ClassAttribute too.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s