Getting issues in object oriented perl - perl

I am new to OO perl. I am trying to write one simple program but getting the error.
Created a package Employee.pm as
package Employee;
sub new {
my $class = shift;
my $self = {};
bless $self, $class;
return $self;
}
sub get_names {
my $self = #_;
print " getting the names \n";
return $self;
}
sub set_names {
my ($self, $last_name) = #_;
$self->{last_name} = $last_name;
return $self->{$last_name};
}
1;
And created a .pl file as
use strict;
use warnings;
use Employee;
my $obj = new Employee("name" => "nitesh", "last_name" => "Goyal");
my $val = $obj->get_names();
print %$val;
my $setName = $obj->set_names("kumar");
print "$setName \n";
I am getting error as
"Can't use string ("1") as a HASH ref while "strict refs" in use at class1.txt line 10."

The error
"Can't use string ("1") as a HASH ref ..
Comes from this part:
sub get_names {
my $self = #_;
When an array is put in scalar context, it returns its size. Since you call the sub with
$obj->get_names();
Only one argument is passed, which is the object, so #_ contains 1 argument, and its size is 1, therefore in the sub get_names, the variable $self is set to 1. Hence the error. What you probably should do is
my $self = shift;
But then, that will not do anything, because you never stored the names in your constructor. As mpapec said, you should do
my $self = { #_ };
in the constructor sub new.
Also, in get_names, you simply return the object, which is not very useful. You should perhaps return $self->{name} and $self->{last_name}.

Related

Not a code reference in Perl class

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};
}

How to return two values in getter method in Perl?

use strict;
use warnings;
package LineSegment;
sub new
{
my $class = shift;
my ($ax, $ay, $bx, $by) = #_;
my $self = {"ax"=>$ax,
"ay"=>$ay,
"bx"=>$bx,
"by"=>$by,
};
bless ($self, $class);
return $self;
}
sub getA{
#Issue on get A
my $self = shift;
return ($self->{ax}, $self->{ay});
}
sub getB{
#Issue on get B
my $self = #_;
return ($self->{bx}, $self->{by});
}
sub setA{
#Can print correct value. Is the return statement where it goes wrong?
my($self, $ax, $ay) = #_;
$self->{ax} = $ax if defined($ax);
$self->{ay} = $ay if defined($ay);
print "Setting ax: $self->{ax}\n";
print "Setting ay: $self->{ay}\n";
return ($self->{ax}, $self->{ay});
}
sub setB{
#Can print correct value. Is the return statement where it goes wrong?
my($self, $bx, $by) = #_;
$self->{bx} = $bx if defined($bx);
$self->{by} = $by if defined($by);
return $self->{bx}, $self->{by};
}
1;
I am trying to create a class called LineSegment. ax and ay are a
point and so are bx and by. I cannot get getA or getB to return what I
want. They only return the second value, which would be ay for getA
and by for getB. I want it to return both values (ax, ay) or (bx,by).
How do I get it to do this? In my setA and setB methods, the values
will print. However, could I be returning them wrong in setA and setB?
Or does my problem lie in my getter methods?
Here is my main:
print "Starting Line Segment\n";
use LineSegment;
$line = new LineSegment(10,20,30,40);
$line->setA(15,10);
$a = $line->getA();
print "Point A is: $a\n";
Here is my Point class:
use strict;
use warnings;
#class name
package Point;
#constructor
sub new
{
my $class = shift;
my($x, $y) = #_;
my $self = {"x"=>$x,
"y"=>$y,
};
bless ($self, $class);
return $self;
}
sub getX{
my($self) = #_;
return $self->{x};
}
sub setX{
my ($self, $x) = #_;
$self->{x} = $x if defined($x);
return $self->{x};
}
sub setY{
my ($self, $y) = #_;
$self->{y} = $y if defined($y);
return $self->{y};
}
sub random{
my $self = shift;
my $range = 50;
$self->{x} = int(rand($range));
$self->{y} = int(rand($range));
return ($self->{x}, $self->{y});
}
1;
Updated main:
use strict;
use warnings;
use Point;
use LineSegment;
my $line = LineSegment->new(Point->new()->random, Point->new()->random);
my $pointA = $line->getA;
my $pointB = $line->getB;
printf "Point = (%d,%d)\n", $pointA->getX, $pointA->getY;
As Tanktalus has pointed out, you are returning a list of two values and expecting to be able to treat it as a single Point object. A list in scalar context evaluates to the last element of the list, so you are getting just the Y coordinate
I've written some functioning code below. One thing that may confuse you is the hash slice syntax #{$self}{qw/ _x _y /} = #_ which is the same as
$self->{_x} = $_[0];
$self->{_y} = $_[1];
You should remember to use strict and use warnings at the top of every Perl source file. You should also avoid using $a and $b as they are used internally by Perl. Longer, more descriptive identifiers are better anyway
If I alter your Point.pm so that its constructor takes parameters (I have also fixed your random method) like this
Point.pm
use strict;
use warnings 'all';
package Point;
sub new {
my $class = shift;
my $self = { };
#{$self}{qw/ _x _y /} = #_ if #_;
bless $self, $class;
}
sub getX{
my $self = shift;
return $self->{_x};
}
sub getY{
my $self = shift;
return $self->{_y};
}
sub setX {
my $self = shift;
$self->{_x} = $_[0] if #_;
return $self->{_x};
}
sub setY {
my $self = shift;
$self->{_y} = $_[0] if #_;
return $self->{_y};
}
use constant RANGE => 50;
sub random {
my $self = shift;
$self->{_x} = int rand RANGE;
$self->{_y} = int rand RANGE;
return $self;
}
1;
and write LineSegment.pm like this
LineSegment.pm
use strict;
use warnings 'all';
package LineSegment;
sub new {
my $class = shift;
my $self = { };
#{$self}{qw/ _pA _pB /} = #_ if #_;
bless $self, $class;
}
sub getA {
my $self = shift;
return $self->{_pA};
}
sub getB {
my $self = shift;
return $self->{_pB};
}
sub setA {
my $self = shift;
$self->{_pA} = $_[0] if #_;
return $self->{_pA};
}
sub setB {
my $self = shift;
$self->{_pB} = $_[0] if #_;
return $self->{_pB};
}
1;
then I can write a program which does what I think you want like this
main.pl
use strict;
use warnings 'all';
use Point;
use LineSegment;
my $line = new LineSegment(
Point->new(10, 20),
Point->new(30, 40),
);
$line->setA( Point->new(15, 10) );
my $point = $line->getA;
printf "Point = (%d,%d)\n",
$point->getX,
$point->getY;
output
Point = (15,10)
my ($ax, $ay) = $line->getA();
getA() is returning a list of variables, you need to receive it into a list of variables. An array would work as well, but this is probably clearer.
But that's not really what you want. What you want to do is to have a line segment be made up of two Point objects (which you may have to create as well), and each Point object store its own x and y coordinates. And then you can return the points as objects, and query their x and y coordinates, e.g.:
my $a_point = $line->getA();
print "Point A is (", $a_point->getX(), ",", $a_point->getY(), ")";
(You can also have the Point class override stringification, but I suspect that's more than you want to think about just yet.)
Apologies for not catching this the first time, but not only are single-letter variable names poor taste in general, $a and $b are particularly bad in perl because they're reserved for the sort function. So I've renamed it here.
With your update, your Point class is missing the getY method. Your main script becomes:
use strict;
use warnings;
use LineSegment;
print "Starting Line Segment\n";
my $line = new LineSegment(10,20,30,40);
$line->setA(15,10);
my $p = $line->getA();
print "Point A is: (", $p->getX(), ",", $p->getY(), ")\n";
and your LineSegment.pm becomes:
package LineSegment;
use strict;
use warnings;
use Point;
sub new
{
my $class = shift;
my #points;
if (#_ == 4)
{
#points = (
Point->new($_[0], $_[1]),
Point->new($_[2], $_[3]),
);
}
else
{
#points = #_;
}
my $self = \#points;
bless ($self, $class);
return $self;
}
sub getA{
#Issue on get A
my $self = shift;
return $self->[0];
}
sub getB{
#Issue on get B
my $self = shift;
return $self->[1];
}
sub setA{
#Can print correct value. Is the return statement where it goes wrong?
my $self = shift;
my $point = $_[0];
if (#_ > 1)
{
$point = Point->new(#_);
}
$self->[0] = $point;
}
sub setB{
my $self = shift;
my $point = $_[0];
if (#_ > 1)
{
$point = Point->new(#_);
}
$self->[1] = $point;
}
1;
This may be a bit overkill, but the right answer is to only pass in/around Point objects in your LineSegment, and let the caller create the Point objects instead of massaging them in here. In my experience, this makes the whole thing clearer.
You have complete answers by Borodin and Tanktalus
showing how to write this class, with other comments. They also emphasize that a segment class should fully utilize a point class.
This is an important point. We encapsulate a certain aspect of our problem in a class. Then we want to use that class for other aspects of the problem, and this is crucial in the object-oriented approach. It usually requires iterations in design and coding, to get those classes right.
This post demonstrates the process by adding a method for the length of a segment, what prompts addition of other methods. I also add a few other pieces to your classes
A couple of utility methods are added in the Point class that are helpful for the length method, and that belong there in general. This is typical -- we desire new functionality and realize that the other classes should provide a part (or all) of it.
Defaults are added to the constructors. Once new is called the objects should be initialized and ready to go, if possible. Your Point::random method is used for this.
A setter and getter are combined in one method, which sets data when called with parameters
Some comments follow the code.
Point.pm
package Point;
use strict;
use warnings;
sub new {
my $class = shift;
my $self = { };
bless $self, $class; # now we can call methods on $self
if (#_) {
#{$self}{qw(_x _y)} = #_; # initialize via parameters
} else {
$self->random(); # initialize using random()
}
return $self;
}
sub x {
my $self = shift;
$self->{_x} = $_[0] if $_[0]; # set if parameter was passed
return $self->{_x};
}
sub y {
my $self = shift;
$self->{_y} = $_[0] if $_[0];
return $self->{_y};
}
sub coords {
my $self = shift;
#{$self}{qw(_x _y)} = #_ if #_;
return $self->{_x}, $self->{_y};
}
sub distance {
my ($self, $pt) = #_;
my ($x1, $y1) = $self->coords();
my ($x2, $y2) = $pt->coords();
return sqrt( ($x1 - $x2)**2 + ($y1 - $y2)**2 );
}
sub random {
my $self = shift;
my $range = $_[0] // 50;
$self->{_x} = int rand $range;
$self->{_y} = int rand $range;
return $self;
}
1;
The random method takes an optional range, so both $pt->random() and $pt->random(10) set random coordinates for $pt. It has default 50, set using defined-or operator, //. Since it returns the object itself you can chain methods, like
my $pt = Point->new(10, 20);
my #coords = $pt->random()->coords();
print "#coords\n";
or, since new itself also returns the object, even
my #coords = Point->new()->random(10)->coords();
This wouldn't be of much use though as we now don't get the object.
LineSegment.pm
package LineSegment;
use strict;
use warnings;
use Point;
sub new {
my $class = shift;
my $self = { };
bless $self, $class;
if (#_) { #{$self}{qw(_pA _pB)} = #_ }
else { #{$self}{qw(_pA _pB)} = (Point->new, Point->new) }
return $self;
}
sub pA {
my $self = shift;
$self->{_pA} = $_[0] if $_[0];
return $self->{_pA};
}
sub pB {
my $self = shift;
$self->{_pB} = $_[0] if $_[0];
return $self->{_pB};
}
sub pts {
my $self = shift;
#{$self}{qw(_pA _pB)} = #_ if #_;
return #{$self}{qw(_pA _pB)};
}
sub len {
my $self = shift;
return $self->{_pA}->distance($self->{_pB});
}
1;
The default in the constructor calls Point's default constructor for each point, if no arguments were passed to initialize the segment object.
The len() method doesn't need coordinates, since we added distance() method to Point. It is natural and needed in a point class and this is better than having LineSegment compute. Often we need to calculate in the class, of course. Think of mid_point (of a segment), intersection (between two segments), etc.
main.pl
use warnings 'all';
use strict;
use feature 'say';
use Point;
use LineSegment;
my $line = LineSegment->new(
Point->new(10, 20),
Point->new(30, 40),
);
my $pt_A = $line->pA( Point->new(15, 10) );
my $pt_B = $line->pB;
printf "Point A = (%d,%d)\n", $pt_A->coords();
printf "Point B = (%d,%d)\n", $pt_B->coords();
printf "Length of the segment: %.3f\n", $line->len();
my #coords = $pt_A->random(10)->coords();
say "Random point, set on existing object: #coords";
my $segm = LineSegment->new();
my #ends = $segm->pts();
print "Segment created with defaults, ends: ";
printf "(%d,%d) ", $_->coords() for #ends;
say '';
This prints
Point A = (15,10)
Point B = (30,40)
Length of the segment: 33.541
Random point, set on existing object: 3 8
Segment created with defaults, ends: (34,19) (16,14)
What is notably missing here are checks of various kinds. However, once that becomes important one should probably start looking toward Moose or the similar but much lighter Moo.
A comment on new LineSegment() syntax used in the question.
A constructor in Perl is just a method, but the one that blesses the object into the class (package). The name new is indeed common but that is merely a convention. Thus the "normal" way to call a constructor is like any other method, ClassName->new().
One can use new ClassName, which is called "indirect object notation" (or syntax). However, here is what perlobj itself has to say about it (original emphasis)
Outside of the file handle case, use of this syntax is discouraged as it can confuse the Perl interpreter. See below for more details.
Also see this post and its links, for example. Just use ClassName->new().

Perl - Can't locate object method via "Module::SUPER"

This is my first time using OOP with perl. I am in the processes of refactoring a 4k line procedural program at work. It seems pretty straight forward but I am having an issue with inheritance and SUPER.
Error message:
"Can't locate object method "New" via package "Module::SUPER" at Module.pm line 10"
I have tried, use base, parent and setting #ISA but they all get the same error. I'm sure I have to be overlooking something.
(This is not code from the program I am working on. Just an example that produces the same error)
All .pm and .pl files are in the same directory in this example. In the program I am working on the main program is in bin and the modules will be in ../modules(relative to bin).
I would assume this would be all I need to make that work:
use lib "../modules";
If I am wrong in thinking that please let me know.
Parent Module
package BaseModule;
use strict;
use warnings;
sub new {
my $class = shift;
my $self = {
ARRAY => shift,
DIVIDER => ","
};
bless ($self, $class);
return $self;
}
sub array {
my $self = shift;
if(#_) { $self->{ARRAY} = shift };
return $self->{ARRAY};
}
sub divider {
my $self = shift;
if(#_) { $self->{DIVIDER} = shift };
return $self->{DIVIDER};
}
sub testSub {
my $self = shift;
print join($self->{DIVIDER}, #{ $self->{ARRAY} } );
return 1;
}
1;
Child Module
package Module;
use strict;
use warnings;
#use base qw(BaseModule);
#require BaseModule;
#our #ISA = qw(BaseModule);
use parent qw(BaseModule);
sub new {
my $class = shift;
my $self = $class->SUPER::New(#_);
$self->{STRING} = shift;
bless ($self, $class);
return $self;
}
sub string {
my $self = shift;
if(#_) { $self->{STRING} = shift };
return $self->{STRING};
}
sub testSub {
my $self = shift;
print "$self->{STRING}:\n";
$self->SUPER::testSub();
return 1;
}
1;
Do I need to bless the child class if the parent class returns an already blessed $self?
Main Script
#!/usr/bin/perl
use strict;
use warnings;
use Module;
my $module = Module->new([1, 2, 3, 4, 5], "Example");
$module->divider(" | "); # Test Changing divider;
$module->testSub();
Any help is greatly appreciated.
"Can't locate object method "New" via package "Module::SUPER" at Module.pm line 10"
You try to call BaseModule::New whis hasn't been defined (did you mean BaseModule::new? Perl is case sensitive).
Do I need to bless the child class if the parent class returns an
already blessed $self?
No, $self at that point is already blesses (you could check that by means of Scalar::Util::blessed().

Access 'self' within an object when other arguments are also commited (perl)

I get an error message while trying to access $self and other variables in a sub inside an object. When the sub is called from outside the object everything works fine. But when I try to access it within the object I get an error (see below).
Here is an example code describing my problem:
package input;
use warnings;
use strict;
sub new {
my $class = shift;
my $self = { };
$self->{_name} = shift;
bless ($self, $class);
return $self;
}
sub test1{
my $self = shift;
my $person = shift;
return $self->{_name}." and ".$person;
}
sub test2{
my $self = shift;
my $person = shift;
print test1($self,$person);
}
package Main;
use warnings;
use strict;
my $i = input->new("Jon");
print $i->test1("Me")."\n";
$i->test2();
The call for print $i->test1("Me")."\n"; does work fine.
I like to access test1() within a different function inside the object.
But for $i->test2(); I get the error
Use of uninitiated value $person in concatenation (.) or String at Line 22.
If I would write
sub test2{
my $self = shift;
my $person = "Jim";
print test1($self,$person);
}
It would work, too.
But I explicitly want to pass some other Variables to the sub besides $self. Since I want to use $self and the other variables. I think it has something to do with passing $self to the sub or not, but I can't figure out how to access $self without the my $self = shift; command.
Use of uninitiated value $person in concatenation (.) or String at Line 22.
When calling test2 method you've forgot $person argument, ie.
$i->test2();
should be
$i->test2("Someone");

How do I insert new fields into $self in Perl, from a File::Find callback

In a Perl object, I'm trying to add a new field into $self from within a File::Find wanted() sub.
use File::Find;
sub _searchForXMLDocument {
my ($self) = #_;
if($_ =~ /[.]+\.xml/) {
$self->{_xmlDocumentPath} = $_;
}
}
sub runIt{
my ($self) = #_;
find (\&_searchForXMLDocument, $self->{_path});
print $self->{_xmlDocumentPath};
}
_searchForXMLDocument() searches for an XML Document within $self->{_path} and is supposed to append that XML path to $self->{_xmlDocumentPath} but when I try to print it, it remains uninitialized. How do I add the field in $self?
Use of uninitialized value in print at /home/scott/workspace/CCGet/XMLProcessor.pm line 51.
You aren't calling _searchForXMLDocument() in an OO manner, so your $self object isn't being passed to it. This should do the trick now. Use a closure for your method and you have access to $self;
sub runIt{
my ($self) = #_;
my $closure = sub {
if($_ !~ m/[.]+\.xml/) {
$self->{_xmlDocumentPath} = $_;
}
};
find(\&$closure, $self->{_path});
print $self->{_xmlDocumentPath};
}
The first argument to find() needs to carry two pieces of information: the test condition, and the object you're working with. The way to do this is with a closure. The sub { ... } creates a code ref, like you get from \&_searchForXMLDocument, but the closure has access to lexical variables in the enclosing scope, so the current object ($self) is associated with the closure.
sub _searchForXMLDocument {
my ($self) = #_;
if($_ =~ /[.]+\.xml/) {
$self->{_xmlDocumentPath} = $_;
}
}
sub runIt{
my ($self) = #_;
find (sub { $self->_searchForXMLDocument (#_) }, $self->{_path});
print $self->{_xmlDocumentPath};
}
I think you're looking for something like this:
package XMLDocThing;
use strict;
use warnings;
use English qw<$EVAL_ERROR>;
use File::Find qw<find>;
...
use constant MY_BREAK = do { \my $v = 133; };
sub find_XML_document {
my $self = shift;
eval {
find( sub {
return unless m/[.]+\.xml/;
$self->{_xmlDocumentPath} = $_;
die MY_BREAK;
}
, $self->{_path}
);
};
if ( my $error = $EVAL_ERROR ) {
die Carp::longmess( $EVAL_ERROR ) unless $error == MY_BREAK;
}
}
...
# meanwhile, in some other package...
$xmldocthing->find_XML_document;
You pass a closure to find and it can access $self from the containing scope. File::Find::find has no capacity to pass in baggage like objects.