Perl: Instantiate complex data structure with constants from subclass - perl

I have a base class that is instantiated with a complex data structure with a three-digit number of entries, all of them constants. A few of those constants are class-specific and should be instantiated with different constants. I am having trouble achieving this. It boils down to this:
tstbase.pm:
package tstbase;
my $THISCLASSCONSTANT = "baseconstant.2";
my %complexdatastructure = (
"attribute.1" => "baseconstant.1",
"attribute.2" => $THISCLASSCONSTANT,
);
sub new {
my $class = shift;
my $self = { };
bless ($self, $class);
$self->_init( $THISCLASSCONSTANT );
return $self;
};
sub _init {
my $self = shift;
$THISCLASSCONSTANT = shift;
foreach (keys %complexdatastructure) {
$self->{$_} = $complexdatastructure{$_};
};
};
tstsubclass.pm:
package tstsubclass;
use parent "tstbase";
my $THISCLASSCONSTANT = "subclassconstant.2";
sub _init {
my $self = shift;
$self->SUPER::_init( $THISCLASSCONSTANT );
};
tst.pl:
#!/usr/bin/perl
use tstbase;
use tstsubclass;
my $baseobj = tstbase->new;
print "Testbase ".$baseobj->{"attribute.1"}." ".$baseobj->{"attribute.2"}."\n";
my $subobj = tstsubclass->new;
print "Testsubclass ".$subobj->{"attribute.1"}." ".$subobj->{"attribute.2"}."\n";
Right now the output is
Testbase baseconstant.1 baseconstant.2
Testsubclass baseconstant.1 baseconstant.2
whereas I want it to be
Testbase baseconstant.1 baseconstant.2
Testsubclass baseconstant.1 subclassconstant.2
Is that possible? I am happy to use
sub THISCLASSCONSTANT = { "subclassconstant.2" }
if it helps. tstsubclass shall not have any baseconstant values.
Right now I instantiate the class with magic strings and do a search & replace. It works, but seems less elegant and performant.
Any help is greatly appreciated. I have asked this question before ( Perl: Using common constructor for base and subclass ) but have over-simplified the example, hence the response could only hint at a possible solution.
Thanks,
Marcus

The simplest way would be to work with references in your %complexdatastructure.
But note that when doing this, $THISCLASSCONSTANT will be changed after the first call to tstsubclass->new.
package tstbase;
my $THISCLASSCONSTANT = "baseconstant.2";
my %complexdatastructure = (
"attribute.1" => \ "baseconstant.1",
"attribute.2" => \ $THISCLASSCONSTANT,
);
sub new {
my $class = shift;
my $self = { };
bless ($self, $class);
$self->_init( $THISCLASSCONSTANT );
return $self;
};
sub _init {
my $self = shift;
$THISCLASSCONSTANT = shift;
foreach (keys %complexdatastructure) {
$self->{$_} = ${$complexdatastructure{$_}};
};
};
Now your output is the desired one, but if you alter the order of the new calls like this:
my $subobj = tstsubclass->new;
print "Testsubclass ".$subobj->{"attribute.1"}." ".$subobj->{"attribute.2"}."\n";
my $baseobj = tstbase->new;
print "Testbase ".$baseobj->{"attribute.1"}." ".$baseobj->{"attribute.2"}."\n";
You'll get:
Testsubclass baseconstant.1 subclassconstant.2
Testbase baseconstant.1 subclassconstant.2
What you could do now is to write your "own" little local (i don't know why the normal local isn't working even with altering the declarations of $THISCLASSCONSTANT to our)
change your tstbase::_init into:
sub _init {
my $self = shift;
my $oldconstant = $THISCLASSCONSTANT;
$THISCLASSCONSTANT = shift;
foreach (keys %complexdatastructure) {
$self->{$_} = ${$complexdatastructure{$_}};
};
$THISCLASSCONSTANT = $oldconstant;
};
Now i think you have what you want.

Related

Object attribute access in Perl

I want to create a Perl class and populate its attributes. The object attributes will be populated like from a yaml files like below.
$data = LoadFile("$mydir/$ARGV[$j]");
my $X= $data->{a}{b}{c};
package Person;
sub new {
my $class = shift;
my $self = {
a=>shift;
};
bless $self, $class;
return $self;
}
my $p=Person->new();
I want to access the attributes like this. How can I do this?
$p->a($data->{a}{b}{c});
$p->a($data->{a}{b}{c}) makes no sense. Do you mean $p->a->{a}{b}{c}?
package Person;
sub new {
my ($class, $data) = #_;
my $self = bless({}, $class);
$self->{data} = $data;
return $self;
}
sub data { $_[0]{data} }
1;
my $data = LoadFile(...);
my $p = Person->new($data);
say $p->data->{a}{b}{c};

How to call a grandparent method in perl

I was recently needing to call the grandparent of a class. Let's say I have:
use strict;
use warnings;
use 5.10.0;
package Superhero;
sub new {
my $class = shift;
return bless {}, $class;
}
sub SaveWorld {
my $self = shift;
my %args = (
hero => "nobody",
#_
);
my $hero = $args{hero};
say "$hero saved the world again";
$self->{status} = "World saved by $hero";
}
sub WorldStatus() {
my $self = shift;
return $self->{status};
}
package Superman;
use parent -norequire, qw(Superhero);
sub SaveWorld {
my $self = shift;
my %args = ( hero => "superman" );
$self->SUPER::SaveWorld(%args);
}
and wanted a new class Spiderman that was like Superman but slightly different:
package Spiderman;
use parent -norequire, qw(Superman);
sub SaveWorld {
my $self = shift;
my $hero = "spiderman";
??? # Call Superhero->SaveWorld
}
for being called as:
my $hero = Spiderman->new;
$hero->SaveWorld();
say $hero->WorldStatus();
It seems quite bad documented how to perform that ??? call.
Something like $self->SUPER::SUPER::SaveWorld($hero) doesn't work (it would look for a package called "SUPER::SUPER").
To make matters worse, the parent itself was dynamic, so a literal $self->Superhero::SaveWorld(%args); was not possible.
The class itself can be easily extracted with my $class = $Superman::ISA[0];, though.
A static call could be achieved with something like:
my $eval = $class . '::SaveWorld($self, %args)';
eval $eval;
which was not working, though (it seems to work as expected in this example, though).
You could use the following:
$self->Superhero::SaveWorld(%args);
Doing so would just compound a broken design. You should fix your design rather than using this. It's unclear what the fix would be because your example makes no sense: A spider man isn't a super man.
The trick is to use a string with the method name.
As found from this answer by Borodin:
my $method = join '::', $class, "SaveWorld";
or just
my $method = "${class}::SaveWorld";
(but note the ${class} to not go over the ::, otherwise it tries ${class::SaveWorld}!)
Thus,
package Spiderman;
use parent -norequire, qw(Superman);
sub SaveWorld {
my $self = shift;
my %args = ( hero => "spiderman" );
my $class = $Superman::ISA[0];
say "I am looking for $class";
my $method = join '::', $class, "SaveWorld";
$self->$method(%args);
}

Declaring multiple objects in class Perl

I have a problem and that is when I want to initialize two objects using the $ self variable. The first object will be initialized but the second will not. Here is some code:
###Class main
use bar;
use arg;
sub new{
my $class = shift;
my $args = shift;
my $self = {};
$self->{'foo'} = $class->SUPER->new($args);
$self->{'bar'} = bar->new();
bless($self, $class);
return $self;
}
sub getFoo{
my $self = shift;
return $self->{'foo'};
}
sub getBar{
my $self = shift;
return $self->{'bar'};
}
For the function getFoo such it returns a value but for getBar the value returned is undef, why does this happen? Thanks
EDIT1
Yes, arg is class's parent and foo is a class that i want to use certain methods.
####class bar
package bar;
sub new{
my $class = shift;
my $self = {
_bar1 => shift // 0,
_bar2 => shift //0,
};
bless($self,$class);
return $self;
}
sub getBar1{
my $self = shift;
return $self->{_bar1};
}
But if I do this now in my class main
$self->{__bar}->getBar1;
The message return is : canĀ“t call method "getBar1" on an undefined value.
You place the object in
$self->{bar}
then you call
$self->{__bar}->getBar1
Change that to
$self->{bar}->getBar1
You appear to have a number of other problems. From your description of your code, Unnamed.pm should contain something like the following: (Don't use main; that's already being used.)
package Unnamed;
use strict;
use warnings qw( all );
use foo qw( );
use bar qw( );
use parent 'arg';
sub new {
my $class = shift;
my $args = shift;
my $self = $class->SUPER::new($args);
$self->{foo} = foo->new();
$self->{bar} = bar->new();
return $self;
}
sub get_foo {
my $self = shift;
return $self->{foo};
}
sub get_bar {
my $self = shift;
return $self->{bar};
}
1;
Then, you can do
my $o = Unnamed->new();
$o->get_bar->bar_method();

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().

How can I use a code ref as a callback in Perl?

I have the following code in my class :
sub new {
my $class = shift;
my %args = #_;
my $self = {};
bless( $self, $class );
if ( exists $args{callback} ) {
$self->{callback} = $args{callback};
}
if ( exists $args{dir} ) {
$self->{dir} = $args{dir};
}
return $self;
}
sub test {
my $self = shift;
my $arg = shift;
&$self->{callback}($arg);
}
and a script containing the following code :
use strict;
use warnings;
use MyPackage;
my $callback = sub {
my $arg = shift;
print $arg;
};
my $obj = MyPackage->new(callback => $callback);
but I receive the following error:
Not a CODE reference ...
What am I missing? Printing ref($self->{callback}) shows CODE. It works if I use $self->{callback}->($arg), but I would like to use another way of invoking the code ref.
The ampersand is binding just to $self and not the whole thing. You can do curlies around the part that returns the reference:
&{$self->{callback}}($arg);
But the
$self->{callback}->($arg);
is generally considered cleaner, why don't you want to use it?