I have been working on a small project and I decided to try and use Method::Signatures because I find it neater.
This is without using Method::Signatures and it works, I'm able to use the package and call methods.
sub new {
my $self = {};
bless($self);
shift;
$self->{parent} = shift;
return $self;
}
But when I try this, it doesn't work:
method new($parent) {
bless {}, $self;
$self->{parent} = $parent;
return $self;
}
I get an error saying: "Can't use string ("PackageName") as hashref while strict refs in use".
Method::Signatures automatically shifts the first argument off the argument list and puts it in $self for you. When you call an object method like $obj->foo, then $self is just $obj. But when you call a class method like Class->method, then $self will be the string Class.
Your $self contains the string PackageName, as it should since you're using new as a class method. Then you use PackageName as an argument to bless, but throw away the result!
bless {}, $self;
This blesses a new empty hashref (the { } ) into the package name contained in $self and then throws the result away. You're then trying to use $self as a hashref when it's just a string, which is causing the error. So try this:
method new($parent) {
my $obj = bless {}, $self;
$obj->{parent} = $parent;
return $obj;
}
method new($parent) {
my $instance = bless {} => $self;
$instance->{parent} = $parent;
return $instance;
}
In this case, $self in your new method will the package name. You then bless an empty anonymous hash into that package which gives you your instantiated object.
Incidentally, you should re-write your plain new method:
sub new {
my $class = shift;
my $self = bless {} => $class;
$self->{parent} = shift;
return $self;
}
Related
I have a series of modules that perform output functions for my scripts. Sometimes the module is called directly -- it is called View -- and sometimes a child class that extends it is used instead (View::ChildName). View declares our $THEMENAME = 'default'; when it is loaded, but the child declares its own specific $THEMENAME when it loads.
Problem: When new() is called on a child theme, it calls my $self = $class->next::method(%params); (using mro) to get some core things set by the parent class before extending it. One of the core bits is that the parent class sets $self->{'themeName'}. However, if it simply calls $THEMENAME, it gets the parent's setting: "default."
The only way I've reliably and successfully solved this is to turn off strict temporarily and do this:
my $packName = ref $self;
{
no strict;
$self->{'themeName'} = ${${packName} . "::THEMENAME"};
}
This works, but in profiling the code, if objects are created frequently, this actually adds more overhead than I expected. I tried the alternative of always using the parent's package name, e.g. the child sets $View::THEMENAME. This works, but only if the theme name is set within new and not on the load of the module; if it is on load, there is erratic behavior if several different child objects (of different children) are created over the course of the script.
These options both seem less than ideal. Is there a good way to do this? The only thing I found was this old question and I think incorporating Moo would probably add more overhead than I'm trying to avoid by getting rid of my current no strict block. Has anything been added to more modern versions of Perl that might solve my issue?
The alternative is to dodge the issue all together and simply set $self->{'themeName'} within each child object's new method, although I'm trying to avoid that change since there's a fair number of legacy child classes that expect $THEMENAME to exist.
Minimal reproducible example of View.pm:
use strict;
package View;
our $THEMENAME = 'default';
sub new {
my $class = shift;
my $params = shift;
my $self = { 'setting' => $params{'setting'} };
bless $self, $class;
$self->{'themeName'} = $THEMENAME;
return $self;
}
And of View/Child.pm:
use strict;
use mro;
package View::Child;
use parent 'View';
our $THEMENAME = 'child';
sub new {
my $class = shift;
my $params = shift;
my $self = $class->next::method($params);
bless $self, $class;
say STDOUT $self->{'themeName'};
# Prints 'default' not 'child'.
return $self;
}
Now a script to call it:
use View::Child;
my $object = View::Child->new();
If you added the first code block to View.pm, it gives the expected result instead, but seems to add about 9 ms to each call to new -- more than the time it takes for it to handle everything else I have in the much longer full length new method -- which adds up if the program runs many iterations:
use strict;
package View;
our $THEMENAME = 'default';
sub new {
my $class = shift;
my $params = shift;
my $self = { };
bless $self, $class;
my $packName = ref $self;
{
no strict;
$self->{'themeName'} = ${${packName} . "::THEMENAME"};
}
return $self;
}
The concept of class properties is one you should forget (in Perl). It's fine for the module to have constants and possibly variables, but they shouldn't be considered part of the class.
I see four approaches you could take:
Update the property in the child's constructor.
Provide the value as a parameter
Override the accessor
Provide the default as a method
Update the property in the child's constructor
# View.pm
sub new {
my ($class, $params) = #_;
my $self = bless({}, $class);
$self->{ setting } = $params->{ setting };
$self->{ themeName } = 'default';
return $self;
}
sub themeName { $_[0]->{themeName} } # Optional
# Child/View.pm
sub new {
my ($class, $params) = #_;
my $self = $class->next::method($params);
$self->{ themeName } = 'child';
return $self;
}
Provide the value as a parameter
# View.pm
sub new {
my ($class, $params) = #_;
my $self = bless({}, $class);
$self->{ setting } = $params->{ setting };
$self->{ themeName } = $params->{ themeName } // 'default';
return $self;
}
sub themeName { $_[0]->{themeName} } # Optional
# Child/View.pm
sub new {
my ($class, $params) = #_;
my $self = $class->next::method({ themeName => 'child', %$params });
return $self;
}
Override the accessor
# View.pm
sub new {
my ($class, $params) = #_;
my $self = bless({}, $class);
$self->{ setting } = $params->{ setting };
return $self;
}
sub themeName { 'default' }
# Child/View.pm
# No need to override `new`.
sub themeName { 'child' }
Provide the default as a method
# View.pm
sub new {
my ($class, $params) = #_;
my $self = bless({}, $class);
$self->{ setting } = $params->{ setting };
$self->{ themeName } = $class->defaultThemeName;
return $self;
}
sub defaultThemeName { 'default' }
sub themeName { $_[0]->{themeName} } # Optional
# Child/View.pm
# No need to override `new`.
sub defaultThemeName { 'child' }
One potential solution would be to add THEMENAME as an overridable method.
View.pm:
use strict;
package View;
our $THEMENAME = 'default';
sub THEMENAME {return 'default'}
sub new {
my $class = shift;
my $params = shift;
my $self = { 'setting' => $params->{'setting'} };
bless $self, $class;
$self->{'themeName'} = $self->THEMENAME;
return $self;
}
View/Child.pm:
use strict;
use mro;
package View::Child;
use parent 'View';
our $THEMENAME = 'child';
sub THEMENAME {return 'child'}
sub new {
my $class = shift;
my $params = shift;
my $self = $class->next::method($params);
bless $self, $class;
say STDOUT $self->{'themeName'};
# Prints 'default' not 'child'.
#
return $self;
}
# perl -Mlib=. -MView::Child -e 'View::Child->new()'
child
My final solution follows what #plentyofcoffee and #ikegami outlined, but I wanted to see if I could come up with a way to set it automatically without each child module implementing it (keeping in mind legacy code). Assuming the child does want to set it, it passes $param{'themeName'} to the parent's constructor, which sets it to $self->{'themeName'}. If themeName is undefined, I came up with this regex in the parent class that extracts the name of the child as a fallback themeName:
unless ($self->{'themeName'}) {
state $getThemeNameRegEx = qr#^SAFARI::(.*::)+(.*?)$#;
$class =~ /$getThemeNameRegEx/;
$self->{'themeName'} = $2 // "default";
}
This sets to default if the name doesn't contain at least two levels below SAFARI, e.g. SAFARI::View is default (the parent module is in use without a child) and SAFARI::View::mysite is mysite.
I'm stumped. I'm new to Perl and after reading some articles, I still can't figure this one out. It's a very small class.
package Haha;
sub new {
$class = shift;
$self = {
path => shift
};
bless $self, $class;
return $self;
}
sub setPath {
my ($self, $new_path) = shift;
$self->(path) = $new_path if defined $new_path;
return $self->(path);
}
sub getPath {
my $self = shift;
return $self->(path);
}
1;
And I used it like this:
use lib 'lib';
use Haha;
my $new_excel = new Haha("sample path");
print $new_excel->getPath() ;
<>;
Class Haha line 23 raises the "Not a code reference" error.
The line that says return $self->(path);
Your class (like most Perl classes) is implemented on top of hashes. When you create a new object in your constructor, you do it like this:
sub new {
$class = shift;
$self = {
path => shift
};
bless $self, $class;
return $self;
}
The line $self = { ... } creates an anonymous hash and stores a reference to that hash in $self. So, $self is a hash reference. Which means that you should access its contents using hash syntax. So your accessor and mutator methods are wrong.
sub setPath {
my ($self, $new_path) = shift;
$self->(path) = $new_path if defined $new_path;
return $self->(path);
}
You are using parentheses, not braces, to access the path value in your hash. The line:
$self->(path) = $new_path if defined $new_path;
Should be:
# Note: braces, not parentheses
$self->{path} = $new_path if defined $new_path;
And the line:
return $self->(path);
Should be:
# Note: braces, not parentheses
return $self->{path};
You need to make a similar fix to getPath().
Unfortunately, the syntax $reference->($value) is completely valid. It means "call the subroutine that you have a reference to in $reference, passing it $value". But, of course, this requires $reference to contain a subroutine reference, not a hash reference.
A few other suggestions.
Always use strict and use warnings.
Indirect object notation ($new_excel = new Haha("sample path")) is likely to burn you at some point. Please use $new_excel = Haha->new("sample path") instead.
Your line my ($self, $new_path) = shift doesn't do what you think it does. You want my ($self, $new_path) = #_.
path is an attribute of the object, use curly brackets:
sub getPath {
my $self = shift;
return $self->{path};
}
In the sub setPath, the variable $new_path is never assigned, use instead:
sub setPath {
my ($self, $new_path) = #_;
$self->{path} = $new_path if defined $new_path;
return $self->{path};
}
sub new
{
my $class = shift; #off the first element
my $self = { };
bless $self, $class;
return $self;
}
Could anyone explain that? What is the use of the following three lines of code?
my $self = { };
bless $self, $class;
return $self;
my $self = { }; creates an anonymous hash reference and stores it in the lexical variable $self.
bless $self, $class; tells Perl that $self is not just any reference but actually an object of the class stored in $class. See bless in perldoc. bless $x, $y returns $x, and a subroutine always returns the value of the last executed statement unless explicetly told otherwise with a return statement, so the next line is optional, but good for readability.
return $self; hands the value in $self (our special object reference) back to the calling function. See return in perldoc.
Edit:
To clarify, if you don't bless your reference, you won't be able to call methods on it. With bless you tell Perl, "look, from now on, associate the reference in $self with the class in $class, so that I can use the methods in that class on the reference."
If in a Perl module I have a 'new' function that declares:
my $self = $class->SUPER::new($pArgsProcessor, $pResponse, kStrFormatHtml);
$self->{mStrRunType} = $pArgsProcessor->readQueryString('runType');
$self->{mStrStartDate} = $pArgsProcessor->readQueryString('startdate');
$self->{mStrEndDate} = $pArgsProcessor->readQueryString('enddate');
bless $self, $class;
return $self;
Is there a way to use the data stored in '$self' in another function? I'm trying to use 'mStrRunType'
$self is probably an object, and all the subs in your package can be called as methods. Then:
my $object = Your::Class->new(...);
$object->foo(42);
Inside the foo method, the object will be the first argument:
sub foo {
my ($self, $meaning_of_life) = #_;
say "mStrEndDate = $self->{mStrEndDate}";
...;
}
Notes:
You should not generally rebless the $self in your constructor. If the superclasses are written to support inheritance, then $class->SUPER::new(...) ensures that the reference is blessed into the correct $class.
You naming scheme suggests you might want to use a more complex data structure:
$self->{mStr} = {
RunType => ...,
StartDate => ...,
EndDate => ...,
};
Your constructor looks correct. Assuming that your constructor is similar to this:
sub new {
my $class = shift;
my $pArgsProcessor, $pResponse, kStrFormatHtml; #shift your constructor params..
my $self = $class->SUPER::new($pArgsProcessor, $pResponse, kStrFormatHtml);
$self->{mStrRunType} = $pArgsProcessor->readQueryString('runType');
$self->{mStrStartDate} = $pArgsProcessor->readQueryString('startdate');
$self->{mStrEndDate} = $pArgsProcessor->readQueryString('enddate');
bless $self, $class;
return $self;
}
Then your method should be able to use your parameters:
sub test {
my $self = shift;
if (defined $self->{mStrEndDate}) {
print $self->{mStrEndDate};
} else {
print "not defined?";
}
}
If your keys are still undefined then make sure that $pArgsProcessor methods are returning defined values.
There is a following class:
package MyClass;
use strict;
use warnings;
sub new
{
my $class = shift();
my $self = {
_class_member => "default"
};
bless ($self, $class);
return $self;
}
How can I set/get the value of the _class_member?
I tried the following code:
sub set_name
{
my $self = shift();
$self->_class_member = shift();
}
But I get the following error:
Can't locate object method "_class_member" via package "MyClass" ...
What am I doing wrong here?
$self is a blessed hash. Unless the you or the original author provided the method _class_member, there's no such method.
You can however, "reach in" the hash to access it:
$self->{'_class_member'} = shift;
This is not a recommended practice for instance values because it's just as easy to type:
$self->{'_vlass_member'} = shift;
without a complaint. Hence the value of accessors.