I'm to learn Perl for a job interview over weekend. In order to get a deeper understanding I'm trying to implement a tree class.
#use strict;
#use warnings;
package Tree;
sub new {
my $class = shift #_;
my $content = shift #_;
my #array = shift #_;
return bless { "content" => $content, "array" => #array }, $class;
}
sub num_children {
my $self = shift #_;
my #array = $self->{"array"};
return scalar #array;
}
return 1;
To test the (faulty) tree class I have implemented the following test script.
#!/usr/bin/perl
require Tree;
my $t = Tree->new("#", undef);
my $tt = Tree->new("*", undef);
my $tttt = Tree->new("-", undef);
my $ttttt = Tree->new(".", undef);
my #list = ();
push #list, $tt;
push #list, $t;
push #list, $tttt;
push #list, $ttttt;
my $ttt = Tree->new("+", #list);
print $ttt->num_children();
Unfortunately the output is 1 instead of my expection of 4. I assume the array is somehow cut off or unvoluntarily converted to a scalar. Any Ideas?
The main problem is that you can't pass arrays as a single value—you have to pass a reference instead.
Also, you should never comment out use strict and use warnings. They are valuable debugging tools, and if you are getting error messages with them enabled you should fix the errors that they are flagging instead.
Here's a working Tree.pm
use strict;
use warnings;
package Tree;
sub new {
my $class = shift;
my ($content, $array) = #_;
return bless { content => $content, array => $array }, $class;
}
sub num_children {
my $self = shift;
my $array = $self->{array};
return scalar #$array;
}
1;
and the calling program tree_test.pl. Note that you should use rather than require a module.
#!/usr/bin/perl
use strict;
use warnings;
use Tree;
my #list = map { Tree->new($_) } ('#', '*', '-', '.');
my $ttt = Tree->new('+', \#list);
print $ttt->num_children, "\n";
output
4
shift only removes one element from an array. Populate #array without it:
my #array = #_;
But, you can't store an array in a hash directly, you have to use a reference:
return bless { content => $content,
array => \#array,
}, $class;
which you then have to dereference:
my #array = #{ $self->{array} };
return scalar #array
Related
I want to pass a hash reference to a module. But, in the module, I am not able to get the hash back.
The example:
#!/usr/bin/perl
#code.pl
use strict;
use Module1;
my $obj = new Module1;
my %h = (
k1 => 'V1'
);
print "reference on the caller", \%h, "\n";
my $y = $obj->printhash(\%h);
Now, Module1.pm:
package Module1;
use strict;
use Exporter qw(import);
our #EXPORT_OK = qw();
use Data::Dumper;
sub new {
my $class = $_[0];
my $objref = {};
bless $objref, $class;
return $objref;
}
sub printhash {
my $h = shift;
print "reference on module -> $h \n";
print "h on module -> ", Dumper $h, "\n";
}
1;
The output will be something like:
reference on the callerHASH(0x2df8528)
reference on module -> Module1=HASH(0x16a3f60)
h on module -> $VAR1 = bless( {}, 'Module1' );
$VAR2 = '
';
How can I get back the value passed by the caller?
This is an old version of perl: v5.8.3
In Module1, change sub printhash to:
sub printhash {
my ($self, $h) = #_;
# leave the rest
}
When you invoke a method on a object as $obj->method( $param1 ) the
method gets the object itself as the first parameter, and $param1 as the second parameter.
perldoc perlootut - Object-Oriented Programming in Perl Tutorial
perldoc perlobj - Perl Object Reference
When you call a sub as a method, the first value in #_ will be the object you called it on.
The first argument you pass to it will be the second value in #_, which you are currently ignoring.
sub printhash {
my ($self, $h) = #_;
Here is what my module looks like:
#!/usr/bin/perl
package Page;
use strict;
use warnings;
use base qw/DBObj/;
our %fields = (
id => undef,
title => '$',
content => '$'
);
sub get_field_names {
my #names = sort keys \%fields;
return \#names;
}
for my $field ( keys %fields ) {
my $slot = __PACKAGE__ ."::$field";
no strict "refs";
*$field = sub {
my $self = shift;
$self->{$slot} = shift if #_;
return $self->{$slot};
}
}
1;
Here the parent module where the strange behaviour occurs
#!/usr/bin/perl
package DBObj;
use strict;
use warnings;
use Data::Dumper;
use DBConn;
sub new {
my $me = shift;
my $class = ref $me || $me;
my $self = {
dbh => new DBConn->new(
dns => '/db.sql',
user => '',
pass => '',
)
};
return bless $self, $class;
}
sub save {
my $self = shift;
my #field_names = #{$self->get_field_names};
print Dumper #field_names;
foreach my $item ( reverse #field_names ) {
print $item;
}
}
sub fill {
my ( $self, $args ) = #_;
foreach my $key ( keys $args ) {
$self->$key( $args->{$key} );
}
}
1;
here is what I am experiencing. This snippet
my #field_names = $self->get_field_names;
print Dumper #field_names;
foreach my $item ( reverse #field_names ) {
print $item;
}
Data::Dumper shows
$VAR1 = [
'content',
'id',
'title'
];
But the foreach loop returns
ARRAY(0x7fc750a26470)
I have a Test::Simple test case where I perform the following test
ok( shift $page->get_field_names eq 'content', 'Page has field content');
so I can shift off an item from the array, but I cannot loop through it which is a puzzle to me.
And please; before you tell me that I shouldn't be doing this and that there is a ton of modules out there I should pick instead, I want to point out; I am doing this our of pure fun, I have been away from Perl for ~10 years and thought it would be fun to play around with it again.
You have made get_field_names return a reference to an array, but you are then putting that reference into an array variable.
Try:
my $field_names = $self->get_field_names;
print Dumper $field_names;
foreach my $item ( reverse #$field_names ) {
print $item;
}
get_field_names returns an arrayref, not an array. Either change its return type by removing the backslash from return \#names; or "cast" its return type to an array by writing:
my #field_names = #{$self->get_field_names};
So I have a class AClass with variables (x, y), and a function which should take two objects as arguments of the same class, compute their x and y, and return a new instance of the class with computed values.
package AClass;
sub new {
my $class = shift;
my $x = shift;
my $y = shift;
my $self = {
x => $x,
y => $y
};
return bless($self, $class);
}
sub getX {
my $self = shift;
return $self->{'x'};
}
sub getY {
my $self = shift;
return $self->{'y'};
}
sub addition {
my ($c1, $c2) = #_;
return new AClass(
$c1->getX() + $c1->getX(),
$c1->getY() + $c2->getY()
);
}
my $a1 = AClass->new(6, 4);
my $a2 = AClass->new(4, 3);
my $val = AClass::addition(\$v1, \$v2);
say $val.getX();
I'm getting error "Can't call method "getX" on unblessed reference". I think the problem is in addition function, when I'm trying to access the values of the objects which are not the real numbers or ?
There is a number of problems here.
You are using $v1 and $v2 when presumably you mean $a1 and $a2
You are passing references to those objects, instead of the objects themselves
Your addition method adds the X value of $c1 to itself instead of to the X value of $c2
You are using the string concatenation operator . instead of the indirection operator ->
It is best to use lower-case letters for lexical identifiers. Capitals are generally reserved for globals like package names
You must always use strict and use warnings at the top of your program. In this case you would have been alerted to the fact that $v1 and $v2 hadn't been declared.
This version of your code works fine
use strict;
use warnings;
package AClass;
sub new {
my $class = shift;
my ($x, $y) = #_;
bless { x => $x, y => $y }, $class;
}
sub get_x {
my $self = shift;
$self->{x};
}
sub get_y {
my $self = shift;
$self->{y};
}
sub addition {
my ($c1, $c2) = #_;
AClass->new(
$c1->get_x + $c2->get_x,
$c1->get_y + $c2->get_y
);
}
package main;
use feature 'say';
my $a1 = AClass->new(6, 4);
my $a2 = AClass->new(4, 3);
my $val = AClass::addition($a1, $a2);
say $val->get_x;
output
10
You use $v1 instead $a1. Always use use strict; use warnings;.
Also, you're taking a reference for no reason.
my $val = AClass::addition($a1, $a2);
The following would also work (though add) would be a better word:
my $val = $a1->addition($a2);
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}.
So I had this crazy idea. Right now, I'm generating a table in Word using Text::CSV_XS like so:
use Win32::OLE;
use Win32::OLE::Const 'Microsoft Word';
use Text::CSV_XS;
# instantiation of word and Text::CSV_XS omitted for clarity
my $select = $word->Selection;
foreach my $row (#rows) { # #rows is an array of arrayrefs
$csv->combine(#{$row});
$select->InsertAfter($csv->string);
$select->InsertParagraphAfter;
}
$select->convertToTable(({'Separator' => wdSeparateByCommas});
But it would be cooler if I could do something like this:
use Win32::OLE;
use Win32::OLE::Const 'Microsoft Word';
use Text::CSV_XS;
# instantiation of word and Text::CSV_XS omitted for clarity
my $select = $word->Selection;
foreach my $row (#rows) { # #rows is an array of arrayrefs
$csv->bind_columns($row);
$csv->print(
sub {
my $something; # Should be the combine()'d string
$select->InsertAfter($something);
$select->InsertParagraphAfter;
},
undef
);
}
$select->convertToTable(({'Separator' => wdSeparateByCommas});
My questions, then, are how do I get the $something out of my second code sample, and what do I need to do to make the closure look like a handle?
Text::CSV::print doesn't necessarily need a handle. The documentation just says it needs something with a print method.
{
package Print::After::Selection;
sub new {
my ($pkg, $selection) = #_;
my $self = { selection => $selection };
return bless $self, $pkg;
}
sub print {
my ($self, $string) = #_;
my $selection = $self->{selection};
$selection->InsertAfter($string);
$selection->InsertParagraphAfter;
return 1; # return true or Text::CSV will think print failed
}
}
...
$csv->print( Print::After::Selection->new( $word->Selection ), undef );
...
(not tested)
I believe you are actually asking how to make data end up in Word using the following interface:
my $fh = SelectionHandle->new($selection);
my $csv = Text::CSV_XS->new({ binary => 1 });
$csv->print($fh, $_) for #rows;
(I think that's a bad idea. It's inside out. Create an object that formats using an encapsulated CSV object instead.)
If you want to given an object the interface of a variable (scalar, array, hash, file handle), you want tie. When you do, you end up with
use SelectionHandle qw( );
use Text::CSV_XS qw( );
my $selection = ...;
my #rows = ...;
my $fh = SelectionHandle->new($selection);
my $csv = Text::CSV_XS->new({ binary => 1 });
$csv->print($fh, $_) for #rows;
And the module looks like:
package SelectionHandle;
use strict;
use warnings;
use Symbol qw( gensym );
use Tie::Handle qw( );
our #ISA = qw( Tie::Handle );
sub new {
my ($class, $selection) = #_;
my $fh = gensym();
tie(*$fh, $class, $selection);
return $fh;
}
sub TIEHANDLE {
my ($class, $selection) = #_;
return bless({
selection => $selection,
}, $class);
}
sub PRINT {
my ($self, $str) = #_;
my $selection = $self->{selection};
$selection->InsertAfter($str);
$selection->InsertParagraphAfter();
return 1;
}
1;