tags:

views:

228

answers:

4

For learning purposes, I am toying around with the idea of building event-driven programs in Perl and noticed that it might be nice if a subroutine that was registered as an event handler could, on failure, just schedule another call to itself for a later time. So far, I have come up with something like this:

my $cb;
my $try = 3;
$cb = sub {
    my $rc = do_stuff();
    if (!$rc && --$try) {
        schedule_event($cb, 10); # schedule $cb to be called in 10 seconds
    } else {
        do_other_stuff;
    }
};
schedule_event($cb, 0); # schedule initial call to $cb to be performed ASAP

Is there a way that code inside the sub can access the coderef to that sub so I could do without using an extra variable? I'd like to schedule the initial call like this.

schedule_event( sub { ... }, 0);

I first thought of using caller(0)[3], but this only gives me a function name, (__ANON__ if there's no name), not a code reference that has a pad attached to it.

+10  A: 

I think Sub::Current will fix your problem.

Leon Timmermans
Thanks. Yes, apparently this does what I want. Too bad it needs to poke around in Perl's internals to do that...
hillu
+4  A: 

If you don't change $cb's value again, you can use that. If not, define a scalar to hold that and don't change it ever again. For example:

my $cb = do {
  my $sub;
  $sub = sub { contents using $sub here }
}
JB
+11  A: 

To get a reference to the current subroutine without using an extra variable, you can use a tool from functional programming, the Y-combinator, which basically abstracts away the process of creating the closure. Here is a perlish version:

use Scalar::Util qw/weaken/;

sub Y (&) {
    my ($code, $self, $return) = shift;
    $return = $self = sub {$code->($self, @_)};
    weaken $self;  # prevent a circular reference that will leak memory
    $return;
}

schedule_event( Y { my $self = shift; ... }, 0);
Eric Strom
Is this correct? I'm not sure what $curry is for. At first I thought my answer was different, but now I think your 3rd "$curry" should be "$code" and you should ditch the $curry. And this answer I think would give you a circular reference with a memory leak.
runrig
@runrig => if you only pass the original code ref into the sub, then any subsequent calls using that ref will no longer receive the coderef as their first argument. There was a circular reference in the code I had. I have updated it to correct the problem.
Eric Strom
Yeah, I got it about 5 minutes after I walked away. I've updated my answer too :-)
runrig
+2  A: 

Using a fixed-point combinator, you can write your $cb function as if the first argument was the function itself:

sub U {
  my $f = shift;
  sub { $f->($f, @_) }
}

my $cb = sub {
  my $cb = shift;
  ...
  schedule_event(U($cb), 10);
  ...
}

schedule_event(U($cb), 0);
runrig
your second call to `schedule_event` will not be passed a copy of $cb that receives itself as its first argument. I've updated my answer to show a version that doesn't leak memory anymore.
Eric Strom
You're right, a callback function is being passed to another function instead of just getting called from the sub, so the sub needs to be wrapped every time. Updated.
runrig