Worlogog::Incident - Lisp-style resumable exceptions (conditions)
use Worlogog::Incident -all => { -prefix => 'incident_' }; use Worlogog::Restart -all => { -prefix => 'restart_' }; sub log_analyzer { incident_handler_bind { for my $log (find_all_logs()) { analyze_log($log); } } [ 'MalformedLogEntryError' => sub { restart_invoke 'skip_log_entry'; # ignore invalid log entries # we could also do this: #restart_invoke 'use_value' => MalformedLogEntry->new(text => $_[0]); # use invalid log entries as-is }, ]; } sub analyze_log { my ($log) = @_; for my $entry (parse_log_file($log)) { analyze_entry($entry); } } sub parse_log_file { my ($file) = @_; open my $fh, '<', $file or die "$0: $file: $!\n"; my @results; while (my $text = readline $fh) { chomp $text; my $entry = restart_case { parse_log_entry($text) } { skip_log_entry => sub { undef }, }; push @results, $entry if $entry; } @results } sub parse_log_entry { my ($text) = @_; if (is_well_formed_log_entry($text)) { return LogEntry->new(... $text ...); # parsing details omitted } restart_case { incident_error MalformedLogEntryError->new(text => $text); } { use_value => sub { $_[0] }, reparse_entry => sub { parse_log_entry($_[0]) }, } }
This module provides resumable exceptions ("conditions") similar to those found in Common Lisp. A condition is a bit like an exception where the exception handler can decide to do other things than unwind the call stack and transfer control to itself.
A note on naming: This module doesn't follow Common Lisp terminology (there are conditions and you can signal them) because I think it would be confusing: The controlling expressions in if or while are also called conditions, and "signal" is more closely associated with %SIG and kill.
if
while
%SIG
kill
Instead I will refer to incidents, which you can report.
The following functions are available:
Executes BLOCK while registering the incident handler(s) speficied by CODEREF or ARRAYREF (see below). If (during the execution of BLOCK) an incident is reported, the innermost registered handler is called with one argument (the incident object).
An incident handler can either return normally, which causes the incident machinery to call the next active handler on the stack, or abnormally, which terminates the incident report. Abnormal returns are possible directly with Return::MultiLevel or indirectly via Worlogog::Restart. That is, an incident handler can abort an incident in progress by invoking a restart that transfers control to an outer point in the running program.
If an ARRAYREF is used, it is passed on to dispatch in Dispatch::Class (which see for details) to assemble the incident handler. The format of the ARRAYREF is that of a dispatch table, i.e. a list of CLASS, CODEREF pairs where CLASS specifies what class of exception objects should be handled (use the pseudo-class :str to match plain strings), and CODEREF is the actual body of the handler.
dispatch
Dispatch::Class
CLASS, CODEREF
:str
Similar to handler_bind above. The main difference is that an incident handler established by handler_case has to decide up front whether to handle the incident, and if it does, the stack is unwound first.
handler_bind
handler_case
The way this is done depends on the second argument: If it's an ARRAYREF, the class of the incident object decides whether a handler (and which one) is entered.
If it's a CODEREF, it is called with the incident object as its first (and only) argument. If the CODEREF returns another coderef, that is taken to be the handler (i.e. the stack is unwound, the returned (inner) coderef is called with the incident object, and the return value of the inner coderef is returned from handler_case). If the CODEREF returns undef, the search for an incident handler that feels responsible continues outwards.
Reports an incident; i.e. it searches the call stack for all appropriate incident handlers registered with handler_bind or handler_case. It calls them in order from innermost (closest to the report call) to outermost (closest to the main program), then returns.
report
An incident handler can decline to handle a particular incident by returning normally. Conversely, an incident handler can stop a report in progress by not returning normally. This can be done by throwing an exception (die), using a non-local return (Return::MultiLevel), or invoking a restart that does one of these things (Worlogog::Restart).
die
Return::MultiLevel
Worlogog::Restart
Incident handlers registered with handler_case implicitly perform a non-local return.
Reports INCIDENT. If no handler takes over and handles the incident, throws INCIDENT as a normal Perl exception. Equivalent to:
sub error { my ($incident) = @_; report $incident; die $incident; }
Works like error with a restart called 'continue' wrapped around it:
error
'continue'
sub cerror { my ($incident) = @_; restart_case { error $incident; } { continue => sub {}, }; }
This way an incident handler can invoke 'continue' to prevent the exception that would normally occur from being thrown.
invoke
Reports INCIDENT. If no handler takes over and handles the incident, outputs INCIDENT as a warning via Carp::carp unless the restart 'muffle_warning' is invoked. Equivalent to:
Carp::carp
'muffle_warning'
sub warn { my ($incident) = @_; restart_case { report $incident; carp $incident; } { muffle_warning => sub {}, }; }
This module uses Exporter::Tiny, so you can rename the imported functions at use time.
Exporter::Tiny
use
Exporter::Tiny, Worlogog::Restart, Return::MultiLevel, Dispatch::Class
Lukas Mai, <l.mai at web.de>
<l.mai at web.de>
Copyright 2013, 2014 Lukas Mai.
This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.
To install Worlogog::Incident, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Worlogog::Incident
CPAN shell
perl -MCPAN -e shell install Worlogog::Incident
For more information on module installation, please visit the detailed CPAN module installation guide.