Callback-Frame
view release on metacpan or search on metacpan
lib/Callback/Frame.pm view on Meta::CPAN
=head1 NAME
Callback::Frame - Preserve error handlers and "local" variables across callbacks
=head1 SYNOPSIS
use Callback::Frame;
my $callback;
frame_try {
$callback = fub {
die "some error";
};
} frame_catch {
my $stack_trace = shift;
print $stack_trace;
## Also, $@ is set to "some error at ..."
};
$callback->();
This will print something like:
some error at tp.pl line 7.
----- Callback::Frame stack-trace -----
synopsis.pl:8 - ANONYMOUS FRAME
synopsis.pl:13 - ANONYMOUS FRAME
=head1 BACKGROUND
When programming with callbacks in perl, you create anonymous functions with C<sub { ... }>. These functions are especially useful because when they are called they will preserve their surrounding lexical environment.
In other words, the following bit of code
my $callback;
{
my $var = 123;
$callback = sub { $var };
}
print $callback->();
will print C<123> even though C<$var> is no longer in scope when the callback is invoked.
Sometimes people call these anonymous functions that reference variables in their surrounding lexical scope "closures". Whatever you call them, they are essential for convenient and efficient asynchronous programming.
For many applications we really like straightforward callback style. The goal of Callback::Frame is to simplify the management of dynamic environments (defined below) while leaving callback style alone.
=head1 DESCRIPTION
The problem that this module solves is that although closures preserve their lexical environment, they don't preserve error handlers or C<local> variables.
Consider the following piece of B<broken> code:
use AnyEvent;
eval {
$watcher = AE::timer 0.1, 0,
sub {
die "some error";
};
};
## broken!
if ($@) {
print STDERR "Oops: $@";
}
AE::cv->recv;
The intent behind the C<eval> above is obviously to catch any exceptions thrown by the callback. However, this will not work because the C<eval> will only be in effect while installing the callback in the event loop, not while running the callback. W...
EV: error in callback (ignoring): some error at broken.pl line 6.
(The above applies to L<EV> which is a well-designed event loop. Other event loops may fail more catastrophically.)
The root of the problem is that the dynamic environment has not been preserved. In this case it is the dynamic exception handlers that we would like to preserve. In some other cases we would like to preserve dynamically scoped (aka "local") variables...
By the way, "lexical" and "dynamic" are the lisp terms. When it applies to variables, perl confusingly calls dynamic scoping "local" scoping, even though the scope is temporal, not local.
Here is how we could fix the code above using L<Callback::Frame>:
use AnyEvent;
use Callback::Frame;
frame_try {
$watcher = AE::timer 0.1, 0, fub {
die "some error";
};
} frame_catch {
print STDERR "Oops: $@";
};
AE::cv->recv;
Now we see the desired error message:
Oops: some error at fixed.pl line 8.
We created two frames to accomplish this: A root frame with C<frame_try> which contains the exception handler, and a nested frame with C<fub> to use as a callback. Unlike C<fub>, C<frame_try> immediately executes its frame. Because the nested callbac...
=head1 USAGE
This module exports the following subs: C<frame>, C<fub>, C<frame_try>, C<frame_catch>, C<frame_local>, and C<frame_void>.
C<frame> is the general interface. The other subs are just syntactic sugar around C<frame>. C<frame> requires at least a C<code> argument which should be a coderef (a function or a closure). It will return another coderef that "wraps" the coderef you...
C<frame> also accepts C<catch>, C<local>, C<existing_frame>, and C<name> parameters which are described below.
C<fub> simplifies the conversion of existing callback code into Callback::Frame enabled code. For example, given the following L<AnyEvent> statement:
$watcher = AE::io $sock, 0, sub { do_stuff() };
In order for the callback to have its dynamic environment maintained, you just need to change it to this:
$watcher = AE::io $sock, 0, fub { do_stuff() };
B<IMPORTANT NOTE>: All callbacks that may be invoked outside the dynamic environment of the current frame should be created with C<frame> or C<fub> so that the dynamic environment will be correctly re-applied when the callback is invoked.
The C<frame_try> and C<frame_catch> subs are equivalent to a call to C<frame> with C<code> and C<catch> parameters. However, unlike with C<frame>, the frame is executed immediately.
C<frame_void> takes a single callback argument. This can be useful if you wish to kick off an unassociated asynchronous action while handling. If the action is run in void context, there is no way for it to throw an exception that will affect your re...
Libraries that wrap callbacks in frames can use the C<Callback::Frame::is_frame()> function to determine if a given callback is already wrapped in a frame. It returns true if the callback is wrapped in a frame and is therefore suitable for use with C...
if (!Callback::Frame::is_frame($callback)) {
$callback = frame(code => $callback);
}
If you wish to run a coderef inside an existing frame's dynamic environment, when creating a frame you can pass in an existing frame as the C<existing_frame> parameter. When this frame is executed, the C<code> of the frame will be run inside C<existi...
frame(existing_frame => $callback, code => sub {
die "request timed out";
})->();
C<existing_frame> is also useful for extracting/setting a callback's local variables.
Although you should never need to, the internal frame stack can be accessed at C<$Callback::Frame::top_of_stack>. When this variable is defined, a frame is currently being executed.
=head1 NESTING AND STACK-TRACES
Callback::Frame tries to make adding error handling support to an existing asynchronous application as easy as possible by not forcing you to pass extra parameters around. It should also make life easier because as a side effect of adding error check...
( run in 0.703 second using v1.01-cache-2.11-cpan-39bf76dae61 )