rpg
rpg

Reputation: 1652

implementing retry logic for subroutine using Sub::Attempts

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?

Upvotes: 0

Views: 909

Answers (5)

Ekkehard.Horner
Ekkehard.Horner

Reputation: 38765

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

Upvotes: 0

Eric Strom
Eric Strom

Reputation: 40152

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', ...;

Upvotes: 1

dgw
dgw

Reputation: 13666

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

Upvotes: 0

BRPocock
BRPocock

Reputation: 13924

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.

Upvotes: 0

Ilion
Ilion

Reputation: 6882

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

Upvotes: 0

Related Questions