Help passing reference to class subroutine in Perl - perl

I am trying to pass a routine to another subroutine within a Perl module. But when I pass the sub reference the passed in ref no longer has the object data. Maybe its not possible to do it this way. The line I have a question about is the "unless" lines below:
sub get_flag_end {
my $self = shift;
return ( -e "$self->{file}" );
}
sub wait_for_end {
my $self = shift;
my $timeout = shift;
my $poll_interval = shift;
# Is it even possible to pass the oject subroutine and retain the objects data?
#unless ( $self->timeout( $timeout, $poll_interval, $self->get_flag_end ) ) { # does not work
unless ( $self->timeout( $timeout, $poll_interval, \&get_flag_end ) ) { # call happens but members are empty
die "!!!ERROR!!! Timed out while waiting for wait_for_end: timeout=$timeout, poll_interval=$poll_interval \n";
}
}
sub timeout {
my $self = shift;
my $timeout = shift;
my $poll_interval = shift;
my $test_condition = shift;
until ($test_condition->() || $timeout <= 0) {
$timeout -= $poll_interval;
sleep $poll_interval;
}
return $timeout > 0; # condition was met before timeout
}
I know that I could change the "get_flag_end" routine to take the value as an argument to the subroutine but what if there was a bunch of stuff done in "get_flag_end" and I needed more members from the object. I simplified the code a bit to make it a little easier to follow.

Just make a closure and pass that in:
sub wait_for_end {
my $self = shift;
my $timeout = shift;
my $poll_interval = shift;
my $callback = sub { $self->get_flag_end() };
unless ( $self->timeout( $timeout, $poll_interval, $callback ) ) {
die "!!!ERROR!!! Timed out while waiting for wait_for_end: timeout=$timeout, poll_interval=$poll_interval \n";
}
}
Update:
The other option is, since timeout is a method of the same class, pass in a method name.
sub wait_for_end {
my $self = shift;
my $timeout = shift;
my $poll_interval = shift;
my $callback = sub { $self->get_flag_end() };
unless ( $self->timeout( $timeout, $poll_interval, 'get_flag_end' ) ) {
die "!!!ERROR!!! Timed out while waiting for wait_for_end: timeout=$timeout, poll_interval=$poll_interval \n";
}
}
sub timeout {
my $self = shift;
my $timeout = shift;
my $poll_interval = shift;
my $method = shift;
# Do whatever
# Now call your method.
$self->$method();
}

In your $test_condition->() line, you are calling the subroutine but not passing it any arguments. Chances are what you meant was $test_condition->($self) or perhaps as $self->$test_condition
Here is a refactor of your code, correcting a few other issues:
sub get_flag_end {
my $self = shift;
return -e $self->{file}; # no need to quote the variable
}
sub wait_for_end {
my ($self, $timeout, $poll_interval) = #_; # unpack many args at once
unless ( $self->timeout( $timeout, $poll_interval, $self->can('get_flag_end') ) ) {
die "!!!ERROR!!! Timed out while waiting for wait_for_end: timeout=$timeout, poll_interval=$poll_interval \n";
}
}
sub timeout {
my ($self, $timeout, $poll_interval, $test_condition) = #_;
until ($self->$test_condition || $timeout <= 0) {
$timeout -= $poll_interval;
sleep $poll_interval;
}
return $timeout > 0; # condition was met before timeout
}
Depending on the rest of your implementation, creating a subroutine that knows its invocant might be better. You can do this in Perl with a closure:
unless ( $self->timeout( $timeout, $poll_interval, sub {$self->get_flag_end} )){
Here a new subroutine is created, which remembers the value of $self. You would call it without arguments $test_condition->()

Related

Tail call Recursion "Optimising"

I have a weird problem I can't figure out. I created a simple sequence in Perl with anonymous functions.
sub{($data, sub{($data, sub{($data, sub{($data, empty)})})})};
And it works but I tired to implement tail optimizing and got some weird behaviour. Example. The iter function below works.
sub iter {
my ($func, $seq) = #_;
my ($data, $next) = $seq->();
if (defined $data) {
$func->($data);
#_ = ($func, $next);#This #_ update works fine
goto &iter;
}
}
while this implementation of iter fails.
sub iter {
my ($func, $seq) = #_;
my ($data, $next) = $seq->();
if (defined $data) {
$func->($data);
$_[1] = $next; #This #_ update fails
goto &iter;
}
}
Both updates of #_ yield the same values for #_ but the code behaves differently when it continues.. To see what I'm talking about try running the complete code below.
#! /usr/bin/env perl
package Seq;
use 5.006;
use strict;
use warnings;
sub empty {
sub{undef};
}
sub add {
my ($data, $seq) = #_;
sub{($data, $seq)};
}
sub iter {
my ($func, $seq) = #_;
my ($data, $next) = $seq->();
if (defined $data) {
$func->($data);
#_ = ($func, $next);#This works fine
#$_[1] = $next; #This fails
goto &iter;
}
}
sub smap {
my ($func, $seq) = #_;
my ($data, $next) = $seq->();
if (defined $data) {
sub{($func->($data), Seq::smap($func, $next))};
}else {
empty();
}
}
sub fold {
my ($func, $acc, $seq) = #_;
my ($data, $next) = $seq->();
if (defined $data) {
#_ = ($func, $func->($acc, $data), $next);
goto &Seq::fold;
}else {
$acc;
}
}
1;
package main;
use warnings;
use strict;
use utf8;
use List::Util qw(reduce);
my $seq =
reduce
{Seq::add($b, $a)}
Seq::empty,
(4143, 1234, 4321, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Seq::iter(sub{my ($data) = #_; STDOUT->print("$data\n")}, $seq);
my $seq2 = Seq::smap(sub{my ($data) = #_; $data * 2}, $seq);
STDOUT->print("\n\n");
Seq::iter(sub{my ($data) = #_; STDOUT->print("$data\n")}, $seq2);
STDOUT->print("\n\n");
my $ans = Seq::fold(sub{my ($acc, $data) = #_; $acc + $data}, 0, $seq);
my $ans2 = Seq::fold(sub{my ($acc, $data) = #_; $acc + $data}, 0, $seq2);
STDOUT->print("$ans\n");
STDOUT->print("$ans2\n");
exit (0);
The code should work for both examples of iter but it doesn't.. Any pointers why?
Writing to $_[1] writes to the second scalar passed to the sub.
$ perl -E'$x = "abc"; say $x; sub { $_[0] = "def"; say $_[0]; }->($x); say $x;'
abc
def
def
So you are clobbering the caller's variables. Assigning to #_ replaces the scalars it contains rather than writing to them.
$ perl -E'$x = "abc"; say $x; sub { #_ = "def"; say $_[0]; }->($x); say $x;'
abc
def
abc
You can replace a specific element using splice.
$ perl -E'$x = "abc"; say $x; sub { splice(#_, 0, 1, "def"); say $_[0]; }->($x); say $x;'
abc
def
abc
It's far more convenient for iterators to return an empty list when they are exhausted. For starters, it allows them to return undef.
Furthermore, I'd remove the expensive recursive calls with quicker loops. These loops can be made particularly simple because of the change mentioned above.
The module becomes:
package Seq;
use strict;
use warnings;
sub empty { sub { } }
sub add {
my ($data, $seq) = #_;
return sub { $data, $seq };
}
sub iter {
my ($func, $seq) = #_;
while ( (my $data, $seq) = $seq->() ) {
$func->($data);
}
}
sub smap {
my ($func, $seq) = #_;
if ( (my $data, $seq) = $seq->() ) {
return sub { $func->($data), smap($func, $seq) };
} else {
return sub { };
}
}
sub fold {
my ($func, $acc, $seq) = #_;
while ( (my $data, $seq) = $seq->() ) {
$acc = $func->($acc, $data);
}
return $acc;
}
1;
Also, for speed reasons, replace
sub { my ($data) = #_; $data * 2 }
sub { my ($acc, $data) = #_; $acc + $data }
with
sub { $_[0] * 2 }
sub { $_[0] + $_[1] }

Perl: Subroutines first argument is not the class

I'm relatively new to Perl so bear with me.
My sub getGenes calls getFeaturesByGeneName in the class SequenceModule. The first loop runs fine however in the second loop it tries to invoke get_SeqFeatures (a BioPerl sub) on the string $name meaning that it skips my $self = shift.
What am I missing?
sub getGenes
{
my #names = shift;
my $genome = shift;
my #cds;
foreach my $name (#names)
{
my $feat = SequenceModule -> getFeatureByGeneName($genome, $name);
push (#cds, $feat);
}
return #cds;
}
...
sub getFeatureByGeneName
{
my $self = shift;
my $seq = shift;
my $name = shift;
my #cds = $seq -> get_SeqFeatures("CDS");
...
}
Speculation: you called getGenes with several names:
getGenes(('name1', 'name2'), $genome);
List don't nest in Perl, so the arguments are flattened:
getGenes('name1', 'name2', $genome);
shift can't return more than one element. Therefore,
my #names = shift;
is equivalent to
my #names;
$names[0] = shift;
The second name is still in #_, so it goes to $genome:
my $genome = shift;
If you need to pass a list to a sub, make it the last argument, or send a reference:
sub getGenes {
my $genome = shift;
my #names = #_;
}
getGenes($genome, 'name1', 'name2');
# OR
sub getGenes {
my $names = shift;
my $genome = shift;
for my $name (#$names) { # dereference
...
}
}
getGenes(['name1', 'name2'], $genome);

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.

Why is the parameter variable empty?

I have a Perl controller class in which I do:
sub func1 {
my $f1 = Model::myModel->new();
my $param = "test";
$f1->func2($param);
}
Model class:
sub new {
my ($class, %arg) = #_;
my $self = bless {}, $class;
return $self;
}
sub func2 {
my ($self, $param) = shift(#_);
warn $param;
}
$param is blank. What is the mistake I am doing?
shift only shifts the first value off of #_. perldoc -f shift will tell you more about how shift works.
You want:
my( $self, $param ) = #_;
You had it right in new(). Not sure what happened ;)
Actually, FYI, your new() will give the warning:
Odd number of elements in hash assignment
If you call it like $package->new( 'a' ); You might want to trap that, something like:
use Carp qw( croak confess );
sub new {
my $class = shift;
confess "$class requires an even number of args" if( #_ & 1 );
my %args = #_;
# ...
}
Or using whatever exception catching mechanism you use.
Try:
sub func2 {
my ( $self, $param ) = #_;
warn $param;
}
or
sub func2 {
my $self = shift #_;
my $param = shift #_;
warn $param;
}

Can't locate object method "add" via package "Heap"

I'm not sure why perl isn't recognizing the Heap's method add. Getting message given in question title. Here are the most relevant files.
#!/usr/bin/perl -w
use strict;
use Util;
use Heap;
use HuffTree;
my $heap = Heap->new;
my $test = 3;
$heap->add($test); # <--------ERROR HERE-----------
package Heap;
use strict;
use warnings;
use POSIX ();
sub new {
my $class = shift;
my $self = { "aref" => [""],
"next" => 1,
#_};
bless $self, $class;
}
sub print {
my $self = shift;
my $next = $self->{"next"};
my $aref = $self->{"aref"};
print "array => #$aref\n";
print "next => $next\n";
}
sub compare {
my ($self, $i, $j) = #_;
my $x = $self->{"aref"}[$i];
my $y = $self->{"aref"}[$j];
if (!defined $x) {
if (!defined $y) {
return 0;
} else {
return -1;
}
}
return 1 if !defined $y;
return $x->priority <=> $y->priority;
}
sub swap {
my ($self, $i, $j) = #_;
my $aref = $self->{"aref"};
($aref->[$i], $aref->[$j]) = ($aref->[$j], $aref->[$i]);
}
sub add {
my ($self, $value) = #_;
my $i = $self->{"next"};
$self->{"aref"}[$i] = $value;
while ($i > 1) {
my $parent = POSIX::floor($i/2);
last if $self->compare($i, $parent) <= 0;
$self->swap($i, $parent);
$i = $parent;
}
$self->{"next"}++;
}
sub reheapify {
my ($self, $i) = #_;
my $left = 2 * $i;
my $right = 2 * $i + 1;
my $winleft = $self->compare($i, $left) >= 0;
my $winright = $self->compare($i, $right) >= 0;
return if $winleft and $winright;
if ($self->compare ($left, $right) > 0) {
$self->swap($i, $left);
$self->reheapify($left);
} else {
$self->swap($i, $right);
$self->reheapify($right);
}
}
sub remove {
my $self = shift;
my $aref = $self->{"aref"};
my $result = $aref->[1];
$aref->[1] = pop #$aref;
$self->{"next"}--;
$self->reheapify(1);
return $result;
}
sub empty {
my $self = shift;
return $self->{"next"} == 1;
}
1;
package HuffTree;
use warnings;
use strict;
use Pair;
our #ISA = "Pair";
sub priority {
my $self = shift;
# lowest count highest priority
return -$self->{frequency};
}
sub left {
my $self = shift;
return $self->{left};
}
sub right {
my $self = shift;
return $self->{right};
}
1;
package Pair;
use warnings;
use strict;
sub new {
my $class = shift;
my $self = { #_ };
bless $self, $class;
}
sub letter {
my $self = shift;
return $self->{letter};
}
sub frequency {
my $self = shift;
return $self->{frequency};
}
sub priority {
my $self = shift;
return $self->{frequency};
}
1;
package Util;
use strict;
use warnings;
sub croak { die "$0: #_: $!\n"; }
sub load_arg_file {
my $path_name = shift #ARGV;
my $fh;
open($fh, $path_name) || croak "File not found.\n";
return $fh;
}
1;
You have a Heap.pm installed from CPAN. That's what gets loaded, not your own Heap.pm. The new sub in the Heap.pm from CPAN looks like this:
sub new {
use Heap::Fibonacci;
return &Heap::Fibonacci::new;
}
Which is actually a bug in said module, because Heap::Fibonacci uses the
standard bless \$h, $class; thing in its new sub,
so the reference is blessed into the Heap package, which
does indeed not have a sub called add (Heap::Fibonacci does).
To solve your immediate problem, you can:
make sure that your module is picked up before the "other" Heap (by modifying #INC with use lib, for example;
or not reinvent the wheel and actually use Heap::Fibonacci).
At any rate, it might be a good idea to report this problem
to the Heap module author - because even if you did not have
your own Heap.pm, your code would still fail with the same message.