implementing retry logic for subroutine using Sub::Attempts - perl

refreshing my question.
Sub::Attempts retries once it find the exception (die).
For me, I want the sub to retry when sub is returning the false value.
Please let me know what should I change to make it work?

If you want to use Sub::Attempts, just make a subroutine that modifies the one you have to make it die rather than return false:
sub die_on_failure {
my $name = (caller).'::'.shift;
my $glob = do {no strict 'refs'; \*$name};
my $code = \&$glob;
no warnings 'redefine';
*$glob = sub {
my $ret = &$code;
$ret ? $ret : die "$name failed"
}
}
Then just do:
die_on_failure 'your_sub_name';
before calling:
attempts 'your_sub_name', ...;

sub retryBeforeFail {
my $className = shift;
my $attempt = shift;
my $max = shift;
my $success = 0;
... main code here ...
if (!$success && $attempt < $max) {
$attempt++;
return $self->retryBeforeFail($attempt, $max);
} else {
return $success;
}
}

Sounds like you need a loop, of some kind. One way to deal with this would be a simple “all done” flag:
sub foo {
my $success = undef;
until ($success) {
# do something interesting
redo if $something_failed;
# do more things here
++$success; # if it all worked properly
# or, exit early on success:
return $something if $all_is_well;
}
}
Without using a temporary var and an until loop, you can also use the special form of goto &subroutine to restart your sub:
sub foo {
# do something interesting
if ($something_failed) {
goto &foo;
}
}
The goto &sub form will throw out local lexical variables, and start the subroutine over again, but it is susceptible to any changes you may have made to #_:
sub foo {
my $x = shift #_;
if ($x < 5) {
#_ = ($x + 1);
goto &foo;
}
return $x;
}
print &foo;
__END__
5
The difference between return &foo(#_) and goto &foo, is that the goto version doesn't add to the call stack — a bit like tail recursion optimization.

Or you could us a simple while loop:
sub retry_before_fail {
my ( $maxtries , $coderef , #args ) = #_ ;
while( $maxtries ) {
# $coderef returns non zero upon success
if( my $result = $coderef->( #args ) ) {
return $result ;
}
$maxtries-- ;
}
# Failure now either return or die
return ;
}

If you have about 60 subs, you could use a wrapper function (idea stolen from HOP)- like this:
sub rpw {
my $f = shift;
my $t = shift;
my $r = &$f(#_);
while ('fail' eq $r && --$t) {
$r = &$f(#_);
}
return $r;
}
to call 'worker' functions (not exactly) like
sub s00 {
my $r = 0.2 > rand() ? 'ok' : 'fail';
print ' in s00 => ', join( '-', #_, $r), "\n";
return $r;
}
sub s01 {
my $r = 0.5 < rand() ? 'ok' : 'fail';
print ' in s01 => ', join( '-', #_, $r), "\n";
return $r;
}
from main code like
print 'from s00 => ', s00(1, 2, 3), "\n";
print 'from s01 => ', s01(qw/a b/), "\n";
print 'from rpw => ', rpw(\&s00, 5, 1, 2, 3), "\n";
print 'from rpw => ', rpw(\&s01, 5, qw/a b/), "\n";
output (not lucky):
in s00 => 1-2-3-fail
from s00 => fail
in s01 => a-b-fail
from s01 => fail
in s00 => 1-2-3-fail
in s00 => 1-2-3-fail
in s00 => 1-2-3-fail
in s00 => 1-2-3-fail
in s00 => 1-2-3-fail
from rpw => fail
in s01 => a-b-fail
in s01 => a-b-ok
from rpw => ok
with a bit of luck:
in s00 => 1-2-3-ok
from s00 => ok
in s01 => a-b-fail
from s01 => fail
in s00 => 1-2-3-fail
in s00 => 1-2-3-fail
in s00 => 1-2-3-fail
in s00 => 1-2-3-ok
from rpw => ok
in s01 => a-b-fail
in s01 => a-b-fail
in s01 => a-b-ok
from rpw => ok

Related

Virtual Filesystem in Perl with Fuse

Anybody help me make a virtual file system in Perl.
Very simple, 2 depth level, as
/subdir
subdir-l2
file2.txt
/file1.txt
I try use Fuse.pm, but not understand how create subdir level. I create %files hash, and if go to subdir, recreate it with new records. It's for test only.
#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use Fuse;
use POSIX qw(ENOENT EISDIR EINVAL);
my (%files) = (
'.' => {
type => 0040,
mode => 0755,
ctime => 1490603721
},
subdir => {
type => 0040,
mode => 0755,
ctime => 1490603721
},
"file1.txt" => {
type => 0100,
mode => 0755,
ctime => 1490603721
}
);
sub filename_fixup {
my ($file) = shift;
$file =~ s,^/,,;
$file = '.' unless length($file);
return $file;
}
sub getdir {
my $tmp = shift;
if ($tmp eq '/') {
return (keys %files),0;
} else {
(%files) = (
'.' => {
type => 0040,
mode => 0755,
ctime => 1490603721
},
# /subdir/subdir-l2
"subdir-l2" => {
type => 0040,
mode => 0755,
ctime => 1490603721
} ,
# /subdir/a-l2.file
"file2.txt" => {
cont => "File 'al2'.\n",
type => 0100,
mode => 0755,
ctime => 1490603721
}
);
return (keys %files),0;
}
}
sub getattr {
my ($file) = filename_fixup(shift);
$file =~ s,^/,,;
$file = '.' unless length($file);
return -ENOENT() unless exists($files{$file});
my ($size) = exists($files{$file}{cont}) ? length($files{$file}{cont}) : 0;
$size = $files{$file}{size} if exists $files{$file}{size};
my ($modes) = ($files{$file}{type}<<9) + $files{$file}{mode};
my ($dev, $ino, $rdev, $blocks, $gid, $uid, $nlink, $blksize) = (0,0,0,1,0,0,1,1024);
my ($atime, $ctime, $mtime);
$atime = $ctime = $mtime = $files{$file}{ctime};
return ($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks);
}
Fuse::main(
mountpoint => "/tmp/123",
getdir => \&getdir,
getattr => \&getattr,
);
one level mount fine, but if go to deeper i get
?????????? ? ? ? ? ? file2.txt
?????????? ? ? ? ? ? subdir-l2
I'm really not a regular user of the Fuse module, neither of FUSE system. Tinkered with this issue out of pure curiosity. Thus, although I can't explain in very much details how to use the plain Fuse module to achieve your goal, I have a working code that does create the wanted filesystem (at least on my system, and seems that it is capable of creating any arbitrary filesystem tree), and I can explain how I got this code working.
So first of all I discovered the Fuse::Simple module on CPAN.
Its SYNOPSIS shows that it provides a really simple API to the Fuse module for creating arbitrary filesystems from a hash structure. Its source code isn't that huge, so I just created 'listing.pl' script file and copied there most of the functions (except fserr that caused a Modification of a read-only value exception), put the main sub contents out, so they will be the main script's flow, hardcoded the filesystem structure ($fs var), and made some little adjustments here and there (like declare vars with my to prevent exceptions), and finally got the filesystem mounted, with all directories listed and files readable. So this is the code I got at last:
#!/usr/bin/env perl
use strict;
use warnings;
use diagnostics;
use Carp;
use Fuse;
use Errno qw(:POSIX); # ENOENT EISDIR etc
use Fcntl qw(:DEFAULT :mode); # S_IFREG S_IFDIR, O_SYNC O_LARGEFILE etc.
use Switch;
my $debug = 0;
my %codecache = ();
my $ctime = time();
my $uid = $>;
my $gid = $) + 0;
my $fs = {
"file1.txt" => "File 1 contents",
"subdir" => {
"subdir-l2" => {},
"file2.txt" => "File 2 contents"
}
};
# some default args
my %args = (
"mountpoint" => "listing",
"debug" => $debug,
"fuse_debug" => 0,
"threaded" => 0,
"/" => $fs
);
# the default subs
my %fs_subs = (
"chmod" => \&fs_not_imp,
"chown" => \&fs_not_imp,
"flush" => \&fs_flush,
"fsync" => \&fs_not_imp,
"getattr" => \&fs_getattr,
"getdir" => \&fs_getdir,
"getxattr" => \&fs_not_imp,
"link" => \&fs_not_imp,
"listxattr" => \&fs_not_imp,
"mkdir" => \&fs_not_imp,
"mknod" => \&fs_not_imp,
"open" => \&fs_open,
"read" => \&fs_read,
"readlink" => \&fs_readlink,
"release" => \&fs_release,
"removexattr" => \&fs_not_imp,
"rmdir" => \&fs_not_imp,
"rename" => \&fs_not_imp,
"setxattr" => \&fs_not_imp,
"statfs" => \&fs_statfs,
"symlink" => \&fs_not_imp,
"truncate" => \&fs_truncate,
"unlink" => \&fs_not_imp,
"utime" => sub{return 0},
"write" => \&fs_write,
);
# except extract these ones back out.
$debug = delete $args{"debug"};
$args{"debug"} = delete( $args{"fuse_debug"} ) || 0;
delete $args{"/"};
# add the functions, if not already defined.
# wrap in debugger if debug is set.
for my $name (keys %fs_subs) {
my $sub = $fs_subs{$name};
# $sub = wrap($sub, $name) if $debug;
$args{$name} ||= $sub;
}
Fuse::main(%args);
sub fetch {
my ($path, #args) = #_;
my $obj = $fs;
for my $elem (split '/', $path) {
next if $elem eq ""; # skip empty // and before first /
$obj = runcode($obj); # if there's anything to run
# the dir we're changing into must be a hash (dir)
return ENOTDIR() unless ref($obj) eq "HASH";
# note that ENOENT and undef are NOT the same thing!
return ENOENT() unless exists $obj->{$elem};
$obj = $obj->{$elem};
}
return runcode($obj, #args);
}
sub runcode {
my ($obj, #args) = #_;
while (ref($obj) eq "CODE") {
my $old = $obj;
if (#args) { # run with these args. don't cache
delete $codecache{$old};
print "running $obj(",quoted(#args),") NO CACHE\n" if $debug;
$obj = saferun($obj, #args);
} elsif (exists $codecache{$obj}) { # found in cache
print "got cached $obj\n" if $debug;
$obj = $codecache{$obj}; # could be undef, or an error, BTW
} else {
print "running $obj() to cache\n" if $debug;
$obj = $codecache{$old} = saferun($obj);
}
if (ref($obj) eq "NOCACHE") {
print "returned a nocache() value - flushing\n" if $debug;
delete $codecache{$old};
$obj = $$obj;
}
print "returning ",ref($obj)," ",
defined($obj) ? $obj : "undef",
"\n" if $debug;
}
return $obj;
}
sub saferun {
my ($sub, #args) = #_;
my $ret = eval { &$sub(#args) };
my $died = $#;
if (ref($died)) {
print "+++ Error $$died\n" if ref($died) eq "ERROR";
return $died;
} elsif ($died) {
print "+++ $died\n";
# stale file handle? moreorless?
return ESTALE();
}
return $ret;
}
sub nocache {
return bless(\ shift, "NOCACHE"); # yup, utter abuse of bless :-)
}
sub dump_open_flags {
my $flags = shift;
printf " flags: 0%o = (", $flags;
for my $bits (
[ O_ACCMODE(), O_RDONLY(), "O_RDONLY" ],
[ O_ACCMODE(), O_WRONLY(), "O_WRONLY" ],
[ O_ACCMODE(), O_RDWR(), "O_RDWR" ],
[ O_APPEND(), O_APPEND(), "|O_APPEND" ],
[ O_NONBLOCK(), O_NONBLOCK(), "|O_NONBLOCK" ],
[ O_SYNC(), O_SYNC(), "|O_SYNC" ],
[ O_DIRECT(), O_DIRECT(), "|O_DIRECT" ],
[ O_LARGEFILE(), O_LARGEFILE(), "|O_LARGEFILE" ],
[ O_NOFOLLOW(), O_NOFOLLOW(), "|O_NOFOLLOW" ],
) {
my ($mask, $flag, $name) = #$bits;
if (($flags & $mask) == $flag) {
$flags -= $flag;
print $name;
}
}
printf "| 0%o !!!", $flags if $flags;
print ")\n";
}
sub accessor {
my $var_ref = shift;
croak "accessor() requires a reference to a scalar var\n"
unless defined($var_ref) && ref($var_ref) eq "SCALAR";
return sub {
my $new = shift;
$$var_ref = $new if defined($new);
return $$var_ref;
}
}
sub fs_not_imp { return -ENOSYS() }
sub fs_flush {
# we're passed a path, but finding my coderef stuff from a path
# is a bit of a 'mare. flush the lot, won't hurt TOO much.
print "Flushing\n" if $debug;
%codecache = ();
return 0;
}
sub easy_getattr {
my ($mode, $size) = #_;
return (
0, 0, # $dev, $ino,
$mode,
1, # $nlink, see fuse.sourceforge.net/wiki/index.php/FAQ
$uid, $gid, # $uid, $gid,
0, # $rdev,
$size, # $size,
$ctime, $ctime, $ctime, # actually $atime, $mtime, $ctime,
1024, 1, # $blksize, $blocks,
);
}
sub fs_getattr {
my $path = shift;
my $obj = fetch($path);
# undef doesn't actually mean "file not found", it could be a coderef
# file-sub which has returned undef.
return easy_getattr(S_IFREG | 0200, 0) unless defined($obj);
switch (ref($obj)) {
case "ERROR" { # this is an error to be returned.
return -$$obj;
}
case "" { # this isn't a ref, it's a real string "file"
return easy_getattr(S_IFREG | 0644, length($obj));
}
# case "CODE" should never happen - already been run by fetch()
case "HASH" { # this is a directory hash
return easy_getattr(S_IFDIR | 0755, 1);
}
case "SCALAR" { # this is a scalar ref. we use these for symlinks.
return easy_getattr(S_IFLNK | 0777, 1);
}
else { # what the hell is this file?!?
print "+++ What on earth is ",ref($obj)," $path ?\n";
return easy_getattr(S_IFREG | 0000, 0);
}
}
}
sub fs_getdir {
my $obj = fetch(shift);
return -$$obj if ref($obj) eq "ERROR"; # THINK this is a good idea.
return -ENOENT() unless ref($obj) eq "HASH";
return (".", "..", sort(keys %$obj), 0);
}
sub fs_open {
# doesn't really need to open, just needs to check.
my $obj = fetch(shift);
my $flags = shift;
dump_open_flags($flags) if $debug;
# if it's undefined, and we're not writing to it, return an error
return -EBADF() unless defined($obj) or ($flags & O_ACCMODE());
switch (ref($obj)) {
case "ERROR" { return -$$obj; }
case "" { return 0 } # this is a real string "file"
case "HASH" { return -EISDIR(); } # this is a directory hash
else { return -ENOSYS(); } # what the hell is this file?!?
}
}
sub fs_read {
my $obj = fetch(shift);
my $size = shift;
my $off = shift;
return -ENOENT() unless defined($obj);
return -$$obj if ref($obj) eq "ERROR";
# any other types of refs are probably bad
return -ENOENT() if ref($obj);
if ($off > length($obj)) {
return -EINVAL();
} elsif ($off == length($obj)) {
return 0; # EOF
}
return substr($obj, $off, $size);
}
sub fs_readlink {
my $obj = fetch(shift);
return -$$obj if ref($obj) eq "ERROR";
return -EINVAL() unless ref($obj) eq "SCALAR";
return $$obj;
}
sub fs_release {
my ($path, $flags) = #_;
dump_open_flags($flags) if $debug;
return 0;
}
sub fs_statfs {
return (
255, # $namelen,
1,1, # $files, $files_free,
1,1, # $blocks, $blocks_avail, # 0,0 seems to hide it from df?
2, # $blocksize,
);
}
sub fs_truncate {
my $obj = fetch(shift, ""); # run anything to set it to ""
return -$$obj if ref($obj) eq "ERROR";
return 0;
}
sub fs_write {
my ($path, $buf, $off) = #_;
my $obj = fetch($path, $buf, $off); # this runs the coderefs!
return -$$obj if ref($obj) eq "ERROR";
return length($buf);
}
Final word: I didn't try to use the module itself (it's not listed in my distro package repository, and I was too lazy (sorry) to install it by cpanm or other way). But I think that if I'll have to just use FUSE with Perl, I'll probably just use Fuse::Simple instead of Fuse, maybe forking it. I'd use plain Fuse only for my academic research, I think...
Hope this helps.

How to specify default values for optional subroutine arguments?

Is there an elegant way to specify default values for subroutine arguments?
Currently, I am using the following approach:
use strict;
use warnings;
func1( "arg1", "arg2", opt1 => "first option", opt2 => 0 );
sub func1 {
my ( $arg1, $arg2, %opt ) = #_;
$opt{opt1} //= "no option";
$opt{opt2} //= 1;
$opt{opt3} //= [];
}
which looks a little bit ugly, when there are many options. I would rather like to do
sub func2 {
my ( $arg1, $arg2, $opt ) = process_args(
opt1 => "no option", opt2 => 1, opt3 => []
);
}
The best I could come up with for this approach was:
sub func2 {
my ( $arg1, $arg2, $opt ) = process_args(
\#_, 2, opt1 => "no option", opt2 => 1, opt3 => []
);
}
sub process_args {
my ($a, $n, %opt_info ) = #_;
my #b = splice #$a, 0, $n;
my %opt = #$a;
for my $key (keys %opt_info) {
$opt{$key} //= $opt_info{$key};
}
return (#b, \%opt);
}
but now I got the other problem, that I must pass \#_ and the number of non-option arguments ( here 2 ), to process_args..
sub func1 {
my $arg1 = shift;
my $arg2 = shift;
my %opt = (
opt1 => 'default',
opt2 => 'default',
#_
);
Or, you can use Params::Validate.
I don't remember seeing a subroutine written expressly to handle subroutine parameters. Do you have a Ruby background?
You can define the options hash by a list of its defaults, followed by anything passed in #_. Like this
use strict;
use warnings;
func1( "arg1", "arg2", opt1 => "first option", opt2 => 0 );
sub func1 {
my ( $arg1, $arg2 ) = splice #_, 0, 2;
my %opts = (
opt1 => "no option",
opt2 => 1,
opt3 => [],
#_,
);
}
Another mechanism, if you want to warn against unsupported paremeters, is to do much as you have, but use delete, and to ensure that the hash is empty afterwards, like this
use strict;
use warnings;
use Data::Dump;
use Carp 'croak';
func1( "arg1", "arg2", opt9 => 9 );
sub func1 {
my ( $arg1, $arg2, %opt ) = #_;
my $opt1 = delete $opt{opt1} // 'no option';
my $opt2 = delete $opt{opt2} // 1;
my $opt3 = delete $opt{opt3} // [];
croak "Unexpected parameters: ", join ',', keys %opt if keys %opt;
}
output
Unexpected parameters: opt9 at E:\Perl\source\args.pl line 16.
main::func1("arg1", "arg2", "opt9", 9) called at E:\Perl\source\args.pl line 7
Try:
# isolate the default options
{
my %default_options = (
opt1 => 'default for opt1',
opt2 => 'default for opt2',
);
sub func1 {
my $arg1 = shift #_;
my $arg2 = shift #_;
# set options
my %opt = %default_options;
if( my %given_opts = #_ ){
for my $key ( keys %opt ){
if( exists $given_opts{$key} ){
$opt{$key} = $given_opts{$key};
}
}
}
# rest of func1
}
}

Perl populating a hash from an array of hashes

I have a script where I am trying to populate a perl hash
I can dereference them fine when I do it individually
while(my($key,$value) = each(%{$spec_hash{'XISX'}})) {
print $key, "," .$value ;
print "\n";
}
while(my($key,$value) = each(%{$spec_hash{'XCBO'}})) {
print $key, "," .$value ;
print "\n";
}
However when i just try and dereference the %spec_hash It only containst one $exch reference, while it should had two - the XISX and the XCBO.
But it never gets to the XCBO.
#!/sbcimp/dyn/data/EVT/GSD/scripts/perl/bin/perl
use FOOConf; # this is our custom DBI module
use Data::Dumper ;
FOOConf::makeDBConnection(production);
my $dbh=$FOOConf::dbh;
my $query = "select e_risk_symbol from gsd_etds where level_name='EXCH_CS' and e_exch_dest='XISX' and e_symbol_comment in ('Bin_6','Bin_56')";
if(!$dbh) {
print "Error connecting to DataBase; $DBI::errstr\n";
}
my $cur_msg = $dbh->prepare($query) or die "\n\nCould not prepare statement:".$dbh->errstr;
$cur_msg->execute();
while (my #row=$cur_msg->fetchrow_array) {
$spec_hash{'XISX'}{$row[0]}=1;
}
$query = "select e_risk_symbol from gsd_etds where level_name='EXCH_CS' and e_exch_dest='XCBO' and e_combo_type='9999'";
if(!$dbh) {
print "Error connecting to DataBase; $DBI::errstr\n";
}
$cur_msg = $dbh->prepare($query) or die "\n\nCould not prepare statement: ".$dbh->errstr;
$cur_msg->execute();
while (my #row=$cur_msg->fetchrow_array) {
$spec_hash{'XCBO'}{$row[0]}=1;
}
#while(my($key,$value) = each(%spec_hash)) {
# print $key, "," .$value ;
# print "\n";
# }
#
# foreach my $exch (sort keys %spec_hash) {
# print "$exch: $spec_hash{$exch}" ;
# }
print Dumper(\%spec_hash);
this is the dumper - shouldn't the dumper contain the XCBO as well?
Why does the hash only have the XISX elements?
$VAR1 = {
'XISX' => {
'FCEL' => 1,
'GPS' => 1,
'MCO' => 1,
'DPZ' => 1,
'WM' => 1,
'SPLS' => 1,
'ILMN' => 1,
'BWLD' => 1,
'CTSH' => 1,
'EWU' => 1,
'MDVN' => 1,
'PDCO' => 1,
'AFAM' => 1,
'SHW' => 1,
}
};
Are you sure that you are populating it with those values?
Try adding a print statement in the while loop, something like this:
while (my #row=$cur_msg->fetchrow_array) {
$spec_hash{'XCBO'}{$row[0]}=1;
print "DEBUG $row[0]\n";
}
My guess is that your query is not returning any results to add to the hash. Unless I missed something, your other code looks fine.

Pass hash to subroutine inside a subroutine already passed that hash

I am working with passing hashes to various subroutines, and I was wondering how to pass a hash to a subroutine and then pass the same hash inside that subroutine to a different subroutine and so on.
For example, the following code works fine.
use strict;
use warnings;
my %hash = (
key1 => 'value1',
key2 => 'value2',
key3 => 'value3',
key4 => '',
);
print %hash, "\n";
check_it(\%hash);
sub check_it {
my $params = shift;
foreach(keys %{$params}){
if($params->{$_}) {
print "'$_' defined as '$params->{$_}'\n";
}
else {
print "'$_' not defined as '$params->{$_}'. Deleting it.\n";
#delete $params->{$_};
$params->{$_} = 'null';
}
}
for ( my $i = 0 ; $i < 7 ; $i++ ) {
print "looping\n";
&check_tags_again(\%hash);
}
}
sub check_tags_again {
my $hash_now = shift;
#check again...
foreach(keys %{$hash_now}){
print "An element of hash: ", $hash_now->{$_}, "\n";
#if(defined $hash_now->{$_}){ delete $hash_now->{$_};}
}
&check_tags_thrice(\%hash);
}
sub check_tags_thrice {
my $hash_now = shift;
#check again...
foreach(keys %{$hash_now}){
print "An element of hash: ", $hash_now->{$_}, "\n";
#if(defined $hash_now->{$_}){ delete $hash_now->{$_};}
}
}
print "final hash:", %hash, "\n";
BUT, when I run the code that follows:
use strict;
use warnings;
use Data::Dumper;
sub process_data {
my $group_size = 10;
my %HoA = (
flintstones => [ "fred", "barney" ],
jetsons => [ "george", "jane", "elroy" ],
simpsons => [ "homer", "marge", "bart" ],
);
&delete_stuff( \%HoA, $group_size );
print "New group:\n";
print Dumper( \%HoA );
undef %HoA;
}
sub delete_stuff {
my $HoARef = shift;
my $group_size = shift;
print "group size in sub $group_size\n";
for ( my $j = 0 ; $j < $group_size ; $j++ ) {
my $dlted = &delete_other_stuff( \%HoA, $j );
print "deleted? '$dlted'\n";
if ( $dlted == 0 ) {
&presence_check( \%HoA, $j );
}
for ( my $i = 0 ; $i < $group_size ; $i++ ) {
}
}
}
sub delete_other_stuff {
my $HoAref = shift;
my $Dex = shift;
return $deleted;
}
sub presence_check {
my $HoAreF = shift;
my $DeX = shift;
}
I get:
Global symbol "%HoA" requires explicit package name at x.pl line 32.
Global symbol "%HoA" requires explicit package name at x.pl line 35.
Execution of x.pl aborted due to compilation errors.
I'm confused because I think it's doing the same thing as the first, but now it claims that %HoA was never initialized.
In delete_stuff, you don't have %HoA, you have $HoARef. If all the subs are expecting a reference to a hash, then you can just use it:
for ( my $j = 0 ; $j < $group_size ; $j++ ) {
my $dlted = &delete_other_stuff( $HoARef, $j );
print "deleted? '$dlted'\n";
if ( $dlted == 0 ) {
&presence_check( $HoARef, $j );
}
...
}
By the way, we're closing on 20 years of Perl 5. There is no reason to call a sub with explicitly passed parameters with an &, which is a Perl 4 holdover.

Recursively printing data structures in Perl

I am currently learning Perl. I have Perl hash that contains references to hashes and arrays. The hashes and arrays may in turn contain references to other hashes/arrays.
I wrote a subroutine to parse the hash recursively and print them with proper indentation. Though the routine works as expected, my instructor was not convinced about the readability and elegance of the below code.
I would really appreciate to get the views of Perl experts here on possible optimization of the below code.
Here is my complete code snippet..
# Array of Arrays
$ref_to_AoA = [
[ "fred", "barney" ],
[ "george", "jane", "elroy" ],
[ "homer", "marge", "bart" ],
];
#Array of Hashes
$ref_to_AoH = [
{
husband => "barney",
wife => "betty",
son => "bamm bamm",
},
{
husband => "george",
wife => "jane",
son => "elroy",
},
];
# Hash of Hashes
$ref_to_HoH = {
flintstones => {
husband => "fred",
pal => "barney",
},
jetsons => {
husband => "george",
wife => "jane",
"his boy" => "elroy", # Key quotes needed.
},
simpsons => {
husband => "homer",
wife => "marge",
kid => "bart",
},
};
# Hash which contains references to arrays and hashes
$finalHash = {
'arrayofArrays' => $ref_to_AoA,
'arrayofHash' => $ref_to_AoH,
'hashofHash' => $ref_to_HoH,
};
$string = str($finalHash);
print "$string\n";
#------------------------------------------------------------------
sub str {
my $hash = shift;
my ($space, $newline, $delimiter) = #_;
$space = "" unless (defined $space);
$newline = "\n\n\n" unless (defined $newline);
$delimiter = "\n--------------------------------------------" unless (defined $delimiter);
my $str = "";
for (sort keys %{$hash}) {
my $value = $hash->{$_};
$str .= "$newline$space$_ == $value$delimiter";
$str .= recurseErrors($value,$space);
}
$str;
}
#------------------------------------------------------------------
sub recurseErrors {
my $str;
my ($value,$space) = #_;
my $ref = ref $value;
if ($ref eq 'ARRAY') {
my $i = 0;
my $isEmpty = 1;
my #array = #$value;
$space .= "\t";
for my $a (#array) {
if (defined $a) {
$isEmpty = 0;
$str .= "\n$space$_\[$i\] :";
$str .= recurseErrors($a,$space);
}
$i++;
}
$str .= "= { }" if ($isEmpty);
} elsif ($ref eq 'HASH') {
$space .= "\t";
for my $k (sort keys %$value) {
if ( ( ref($value->{$k}) eq 'HASH') || (ref $value->{$k} eq 'ARRAY') ) {
my $val = $value->{$k};
$str .= "\n\n$space$k == ";
$str .= "$val";
}
else {
$str .= "\n$space$k == ";
}
$str .= recurseErrors($value->{$k},$space);
}
# we have reached a scalar (leaf)
} elsif ($ref eq '') {
$str .= "$value";
}
$str
}
#------------------------------------------------------------------
Output:
arrayofArrays == ARRAY(0x9d9baf8)
--------------------------------------------
arrayofArrays[0] :
arrayofArrays[0] :fred
arrayofArrays[1] :barney
arrayofArrays[1] :
arrayofArrays[0] :george
arrayofArrays[1] :jane
arrayofArrays[2] :elroy
arrayofArrays[2] :
arrayofArrays[0] :homer
arrayofArrays[1] :marge
arrayofArrays[2] :bart
arrayofHash == ARRAY(0x9d9bba8)
--------------------------------------------
arrayofHash[0] :
husband == barney
son == bamm bamm
wife == betty
arrayofHash[1] :
husband == george
son == elroy
wife == jane
hashofHash == HASH(0x9da45f8)
--------------------------------------------
flintstones == HASH(0x9d9bb48)
husband == fred
pal == barney
jetsons == HASH(0x9d9bbf8)
his boy == elroy
husband == george
wife == jane
simpsons == HASH(0x9d9bc48)
husband == homer
kid == bart
wife == marge
Always use use strict;
To be a good boy, use use warnings as well.
The names you use for subroutines should make it obvious what the subroutine does. "recurseErrors" kind of violates that principle. Yes, it does recurse. But what errors?
On the first line of each subroutine you should declare and initialize any parameters. recurseErrors first declares $str and then declares its parameters.
Don't mix shift and = #_ like you do in str()
You might consider breaking up what is now called recurseErrors into specialized routines for handling arrays and hashes.
There's no need to quote variables like you do on lines 99 and 109.
Apart from that I think your instructor had a bad day that day.
maybe Data::Dumper is what you want:
use Data::Dumper;
$str = Dumper($foo);
print($str);
If you are new to perl, I'd recommend running your code through perl-critic (there is also a script you can install from CPAN, normally I use it as a test so it gets run from the command line whenever I do "make test"). In addition to its output, you might want to break up your functions a bit more. recurseErrors has three cases that could be split into sub functions (or even put into a hash of ref-type to sub-function ref).
If this were a production job, I'd use Data::Dumper, but it sounds like this is homework, so your teacher might not be too pleased.
Here is one simple example why your code is not easily readable:
$delimiter = "\n--------------------------------------------" unless (defined $delimiter);
You could use the defined or operator:
$delimiter //= "\n" . '-' x 44;
If you are worried about earlier Perls:
defined $delimeter or $delimeter = "\n" . '-' x 44;
Conditionals going off the right margin are enough of a turn-off for me not to read the rest of the code.
My guess is that he doesn't like that you
expect a hash in the str function.
call the same function to print arrays as hashes, despite that there appears to be no common function between them.
allow various ways to call str, but it never figures into the final result.
allow configurable space to be passed in to the root function, but have a tab hardcoded in the recursive function.
omit undefined values that actually hold a place in the arrays
Those are issues that I can see, pretty quickly.
You could have separated out the code blocks that dealt with arrays, and hashes.
sub recurse{
...
recurse_A(#_) if $ref eq 'ARRAY';
recurse_H(#_) if $ref eq 'HASH';
...
}
sub recurse_A{ ... }
sub recurse_H{ ... }
I would recommend starting out your subroutines like this, unless you have a real good reason for doing otherwise.
sub example{
my( $one, $two, $three, $optional_four ) = #_;
( If you do it like this then Komodo, at least, will be able to figure out what the arguments are to your subroutine )
There is rarely any reason to put a variable into a string containing only the variable.
"$var" eq $var;
The only time I can think I would ever do that is when I am using an object that has an overloaded "" function, and I want to get the string, without also getting the object.
package My_Class;
use overload
'""' => 'Stringify',
;
sub new{
my( $class, $name ) = #_;
my $self = bless { name => $name }, $class;
return $self;
}
sub Stringify{
my( $self ) = #_;
return $self->{name};
}
my $object = My_Class->new;
my $string = "$object";
I've struggled with this same problem before, and found my way here. I almost used a solution posted here, but found a more suitable one (for me anyway). Read about Depth First Recursion here.
The sub in the above article works perfectly with a reference containing other Hashes, Arrays, or Scalars. It did not print Hash key names, though, so I slightly modified it:
#!/usr/bin/perl
#
# See:
#
# http://perldesignpatterns.com/?DepthFirstRecursion
#
use strict;
use warnings;
my %hash = (
'a' => {
'one' => 1111,
'two' => 222,
},
'b' => [ 'foo', 'bar' ],
'c' => 'test',
'd' => {
'states' => {
'virginia' => 'richmond',
'texas' => 'austin',
},
'planets' => [ 'venus','earth','mars' ],
'constellations' => ['orion','ursa major' ],
'galaxies' => {
'milky way' => 'barred spiral',
'm87' => 'elliptical',
},
},
);
&expand_references2(\%hash);
sub expand_references2 {
my $indenting = -1;
my $inner; $inner = sub {
my $ref = $_[0];
my $key = $_[1];
$indenting++;
if(ref $ref eq 'ARRAY'){
print ' ' x $indenting,'ARRAY:';
printf("%s\n",($key) ? $key : '');
$inner->($_) for #{$ref};
}elsif(ref $ref eq 'HASH'){
print ' ' x $indenting,'HASH:';
printf("%s\n",($key) ? $key : '');
for my $k(sort keys %{$ref}){
$inner->($ref->{$k},$k);
}
}else{
if($key){
print ' ' x $indenting,$key,' => ',$ref,"\n";
}else{
print ' ' x $indenting,$ref,"\n";
}
}
$indenting--;
};
$inner->($_) for #_;
}
#use strict ;
use warnings ;
# use module
use XML::Simple;
use Data::Dumper;
#debug print "START SCRIPT " ;
my $fileToParse = 'C:/Temp/CDIP/scripts/perl/nps_all_workflows.xml' ;
# create object
my $objXml= new XML::Simple;
# read XML file
my $data = $objXml->XMLin("$fileToParse");
# #debug print "\n FirstLevel is " . $objXml->{'POWERMART'} ;
my $level = 1 ;
#
printHashKeyValues ($data ) ;
sub printHashKeyValues
{
$level ++ ;
my $refHash = shift ;
my $parentKey = shift ;
my $parentValue = shift ;
while( my ($key, $value) = each %$refHash)
{
if ( defined ( $key ) )
{
if ( ref ($refHash->{"$key"}) eq 'HASH' )
{
my $newRefHash = $refHash->{"$key"} ;
#debug print " \n The key is a hash " ;
printHashKeyValues ($newRefHash , $key , $value) ;
}
if ( ref ($refHash->{"$key"}) eq 'ARRAY' )
{
#debug print " \n the key is an ARRAY " ;
printArrayValues ( $refHash->{"$key"} ) ;
}
} #eof if ( defined ( $key ))
if ( defined ( $value) )
{
if ( ref ($refHash->{"$value"}) eq 'HASH' )
{
my $newRefHash = $refHash->{"$value"} ;
#debug print " \n The value is a hash " ;
printHashKeyValues ($newRefHash , $key , $value) ;
}
if ( ref ($refHash->{"$value"}) eq 'ARRAY' )
{
#debug print " \n the value is an ARRAY " ;
printArrayValues ( $refHash->{"$value"} ) ;
}
} #eof if defined ( $value )
#debug print "\n key: $key, value: $value.\n";
} #eof while
} #eof sub
sub printArrayValues
{
my $arrRef = shift ;
my #array = #$arrRef;
my $parrentArrayElement = shift ;
#debug print "printArrayValues CALLED " ;
foreach my $arrayElement ( #array )
{
if (defined ( $arrayElement ) )
{
if ( ref ($arrayElement) eq 'HASH' )
{
#debug print " \n The \$arrayElement is a hash FROM THE ARRAY " ;
printHashKeyValues ($arrayElement ) ;
} #eof if
if ( ref ($arrayElement) eq 'ARRAY' )
{
#debug print " \n The \$arrayElement is a ARRAY FROM THE ARRAY " ;
printArrayValues ($arrayElement ) ;
} #eof if
#debug print "\n \$arrayElement is $arrayElement " ;
} #eof if ( defined ( $arrayElement ) )
} #eof foreach
} #eof sub
# #debug print output
##debug print Dumper($data);
1 ;