Callback-Frame
view release on metacpan or search on metacpan
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
BACKGROUND
When programming with callbacks in perl, you create anonymous functions
with "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 123 even though $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.
DESCRIPTION
The problem that this module solves is that although closures preserve
their lexical environment, they don't preserve error handlers or "local"
variables.
Consider the following piece of 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 "eval" above is obviously to catch any exceptions
thrown by the callback. However, this will not work because the "eval"
will only be in effect while installing the callback in the event loop,
not while running the callback. When the event loop calls the callback,
it will probably wrap its own "eval" around the callback and you will
see something like this:
EV: error in callback (ignoring): some error at broken.pl line 6.
(The above applies to 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 (see below).
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 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 "frame_try"
which contains the exception handler, and a nested frame with "fub" to
use as a callback. Unlike "fub", "frame_try" immediately executes its
frame. Because the nested callback frame is created while the root frame
is executing, the callback will preserve the dynamic environment
(including the exception handler) of the root frame.
USAGE
This module exports the following subs: "frame", "fub", "frame_try",
"frame_catch", "frame_local", and "frame_void".
"frame" is the general interface. The other subs are just syntactic
sugar around "frame". "frame" requires at least a "code" argument which
should be a coderef (a function or a closure). It will return another
coderef that "wraps" the coderef you passed in. When this wrapped codref
is run, it will reinstate the dynamic environment that was present when
the frame was created, and then run the coderef that you passed in as
"code".
"frame" also accepts "catch", "local", "existing_frame", and "name"
parameters which are described below.
"fub" simplifies the conversion of existing callback code into
Callback::Frame enabled code. For example, given the following 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() };
IMPORTANT NOTE: All callbacks that may be invoked outside the dynamic
environment of the current frame should be created with "frame" or "fub"
so that the dynamic environment will be correctly re-applied when the
callback is invoked.
The "frame_try" and "frame_catch" subs are equivalent to a call to
"frame" with "code" and "catch" parameters. However, unlike with
"frame", the frame is executed immediately.
"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 request, or to access its local
variables. Note that you probably should install a separate
"frame_catch" in case the unassociated operation throws exceptions.
( run in 1.974 second using v1.01-cache-2.11-cpan-39bf76dae61 )