I recently read The D Programming Language by Andrei Alexandrescu (which I highly recommend). D features an interesting (and, to the best of my knowledge, unique) alternative to try/catch-style error handling (although it also supports try/catch/finally). It turns out that very little code actually traps errors and makes decisions based on error conditions. In most cases, the error must be temporarily trapped to allow cleanup before re-throwing the error afterward. In these cases, D programmers can use a scope statement to register clean-up code to be executed should any of the statements following it trigger an error.
Imagine a function to mix two chemicals to trigger some reaction. You may have a container class that knows its own operating parameters as well as a recipe class that knows the chemical components and mixture rates.
void mix_dangerous_chemicals(Container c, Recipe r) { while (!r.is_complete()) { scope(exit) c.set_temp(r.get_temp(r.MIX_FAILURE)); scope(exit) c.seal(); // Add ingredients to the container c.add(r.ingredients, r.mix_rate); // If operating outside tolerances for the // container, trigger an error. c.validate_operating_parameters(); // Check that the recipe is producing the // expected results, perhaps throwing an error // if the recipe sees that a reaction appears to // be ready to breach container tolerances. r.validate_procedure(c); } }
This does something interesting. The scope statements each eat up all statements coming after them within their lexical scope and convert them into try/finally blocks. Statements are stack-based, so they are executed in the order opposite of declaration. This is because the first statement is wrapping the second scope statement in a try block, and then placing itself in the finally block. Something like this:
void mix_dangerous_chemicals(Container c, Recipe r) { while (!r.is_complete()) { try { try { // Add ingredients to the container c.add(r.ingredients, r.mix_rate); // If operating outside tolerances for the // container, trigger an error. c.validate_operating_parameters(); // Check that the recipe is producing the // expected results, perhaps throwing an error // if the recipe sees that a reaction appears to // be ready to breach container tolerances. r.validate_procedure(c); } finally { c.seal(); } } finally { c.set_temp(r.get_temp(r.MIX_FAILURE)); } } }
Which is cool, because the first syntax is much easier to read. You can imagine what this does for really hairy error handling.
Filter::Cleanup makes this syntax possible in Perl using source filtering and PPI. PPI is used to ensure that the rest of a cleanup statement's scope is addressed and correctly rewritten (because let's face, correctly parsing Perl with regexes is hard). Using the cleanup source filter, the above code would be written like this:
use Filter::Cleanup; sub mix_dangerous_chemicals { my ($c, $r) = @_; until ($r.is_complete()) { cleanup { $c.set_temp($r.get_temp($r.MIX_FAILURE)) }; cleanup { $c.seal() }; $c.add($r.ingredients, $r.mix_rate); $c.validate_operating_parameters(); $r.validate_procedure(); } }]]>
Reddit::Client begins its life at 0.02 with mostly complete unit tests (thanks for the footnote, Gabor), some refactoring, and a few bug fixes. Enjoy!
]]>It is pretty simple to use, and the docs are complete, but here is the gist:
use Reddit::API;
my $session_file = '~/.reddit';
my $reddit = Reddit::API->new(session_file => $session_file);
unless ($reddit->is_logged_in) {
$reddit->login('someone', 'secret');
$reddit->save_session();
}
$reddit->submit_link(
subreddit => 'perl',
title => 'Perl is still alive!',
url => 'http://www.perl.org'
);
my $links = $reddit->fetch_links(subreddit => '/r/perl', limit => 10);
foreach (@{$links->{items}}) {
...
}
]]>