catch me if you can: sugary exception handling in perl
DESCRIPTION
Exception handling in perl is currenlty far from perfect. eval leaves a lot to be desired. Lots of {dark,cpan} code is rife with pottential code that could interfere with $@ TryCatch (on CPAN now) fixes this through the magic of Devel::Declare. Breifly address what's wrong with eval, what I/we'd like, and then finally how it was done using Devel::DeclareTRANSCRIPT
YAPC EU 2009 = ASH BERLIN ‹ %DEVEL DECLARE / SCOPE UPPER ‹eASH BERLIN aASH BERLIN ½ MST < FLORIAN RAGWITZ / VINCENT PIT
£MARK FOWLER / THE O’REILLY CAMEL
EXCEPTION HANDLING IN PERL
if you cancatch me
Tuesday, 11 August 2009
Catch Me If You Can:Sugary exception handling with TryCatch.pm
Ash BerlinYAPC::EU 2009 – Lison
A.K.A The please don’t sue me Title
Tuesday, 11 August 2009
Perl Sucks!(and what to do about it)
Image courtesy of Mark Fowler
YAPC::EU 2007 – Vienna
Tuesday, 11 August 2009
Perl Sucks?
• Of course it does, it’s software
• Installing Modules is hard
• Perl programs are just scripts
• Its Exception handling is hateful
Tuesday, 11 August 2009
eval {};sucks
Tuesday, 11 August 2009
eval {};sucks
Tuesday, 11 August 2009
eval sucks
• Sucky syntax
• Easy to get (subtly) wrong.
• return doesn’t work as expected
• eval BLOCK vs eval EXPR
• $@ is global
Tuesday, 11 August 2009
“But Perl already has exception handling”
Lots of Exception::* and Error::* modules
Tuesday, 11 August 2009
None of them Perfect
• Some are just an exception framework
• Good; doesn’t solve catching problem
• Catching errors shouldn’t be hard
Tuesday, 11 August 2009
I want the Moon on a Stick
Tuesday, 11 August 2009
I want the Moon on a Stick
Tuesday, 11 August 2009
Exceptions in JavaScript
try { someCallThatDies();}catch (e if e instanceof Error) {}catch (f if f.length < 10) {}catch (g) { /* otherwise */ }
Tuesday, 11 August 2009
The same in Perl
if (blessed($@) && $@->isa(‘Error’)) { my $e = $@;}elsif (len($@) < 10 ) { my $f = $@; }elsif ($@) { my $g = $@;}
eval { some_call_that_dies();};
Tuesday, 11 August 2009
The same in PerlDone properly
{ local $@; eval { some_call_that_dies(); }; if (my $_e = $@) { if (blessed($_e) && $_e->isa(‘Error’)) { my $e = $_e; } elsif (len($_e) < 10 ) { my $f = $_e; } else { my $g = $_e; } }}
Tuesday, 11 August 2009
That’s a lot to write every time
Not very DRY
Tuesday, 11 August 2009
Using Error.pm
Looking good so far…
use Error qw(:try);try { some_call_that_dies(); }catch Error with { my $e = shift;}
Tuesday, 11 August 2009
Using Error.pm
otherwise { my $f = shift; if (length($f) < 10) { } else { my $g = $f; }} Less good :(
use Error qw(:try);try { some_call_that_dies(); }catch Error with { my $e = shift;}
Tuesday, 11 August 2009
Problems with Error.pm
• (Not to single it out as bad)
• Its Exception Object class
• AND try/catch implementations
• sub {} closures are slow
• Speed for error free path is important
Tuesday, 11 August 2009
TryCatch.pm
use TryCatch;try { some_call_that_dies();}catch (Error $e) {}catch ($f where {length < 10}) {}catch ($g) {} # otherwise
Tuesday, 11 August 2009
TryCatch syntax
• try must be followed by a block
• Followed by zero or more catch clauses
• catch may be followed by a signature
• catch must also be followed by a block
Tuesday, 11 August 2009
Try syntax
• try keyword
• Followed by a block
• That was easy :)
Tuesday, 11 August 2009
Catch syntax
• catch keyword
• Followed by an optional signature:
(MyError $e where { } )
• Followed by a block.
Tuesday, 11 August 2009
Catch Sig syntax
• MyError is any valid Moose type constraint
• Optional
• Uses MooseX::Types for preference
• Else falls back to Moose’s string parsing
• Never quoted, always bare
• Example:
Str|ArrayRef[MyError]
(MyError $e where { } )
Tuesday, 11 August 2009
Catch Sig syntax
• $e is the variable name for the error
• Will be created as “my $e” in the block
• A variable name is required
(MyError $e where { } )
Tuesday, 11 August 2009
Catch Sig syntax
• Constraint (not type constraint)
• $_ is the error being tested.
• Much like grep or map, return truthy value
• PPI used to just get ‘everything in {}’
(MyError $e where { } )
Tuesday, 11 August 2009
Syntax Notes
• try can be nested as deep as you like
• You can use return to exit the sub.
• No finally. Yet. It’s on my TODO
Tuesday, 11 August 2009
TryCatch examplesub foo {
try { die MyError->new($_[0]) if $_[1] eq “class”; die $_[0] if $_[1] eq “plain”;
return “value”;}catch (MyError $e) { }catch ($e where { /No Cheese!/ }) { }catch ($e) { } # otherwise
}
Tuesday, 11 August 2009
TryCatch examplesub foo {
try { die MyError->new($_[0]) if $_[1] eq “class”; die $_[0] if $_[1] eq “plain”;
return “value”;}catch (MyError $e) { }catch ($e where { /No Cheese!/ }) { }catch ($e) { } # otherwise
}
This will return a value from foo, not just the try/eval
i.e. use return natually
Tuesday, 11 August 2009
sub foo {try { die MyError->new($_[0]) if $_[1] eq “class”; die $_[0] if $_[1] eq “plain”;
return “value”;}catch (MyError $e) { }catch ($e where { /No Cheese!/ }) { }catch ($e) { } # otherwise
}
TryCatch exampleMoose type constraints
handle the “is this a blessed object” and similar checks
Tuesday, 11 August 2009
TryCatch exampleIf this wasn’t here, and the plain error was thrown, it
would get re-thrown.
sub foo {try { die MyError->new($_[0]) if $_[1] eq “class”; die $_[0] if $_[1] eq “plain”;
return “value”;}catch (MyError $e) { }catch ($e where { /No Cheese!/ }) { }catch ($e) { } # otherwise
}
Tuesday, 11 August 2009
ImplementationHere Be (anthropomorphic) Dragons
xkcd.com
Tuesday, 11 August 2009
Source Filters
• Have to look through the entire source
• And only change the bits they want.
• Perl is notoriously hard to parse
• Can cause odd bugs:
Tuesday, 11 August 2009
package ShootMeInTheHead;
use Moose;use Switch;
sub foo { my ($variable) = @_; return $variable + 1;}sub bar { my ($variable) = @_; return $variab1e + 1;}}
Source Filters
Global symbol "$variab1e" requires explicit package name at line 18.
Tuesday, 11 August 2009
package ShootMeInTheHead;
use Moose;use Switch;
sub foo { my ($variable) = @_; return $variable + 1;}sub bar { my ($variable) = @_; return $variab1e + 1;}}
Source Filters
Global symbol "$variab1e" requires explicit package name at line 18.
# load switch statement
Tuesday, 11 August 2009
package ShootMeInTheHead;
use Moose;use Switch;
sub foo { my ($variable) = @_; return $variable + 1;}sub bar { my ($variable) = @_; return $variab1e + 1;}}
Source Filters
Global symbol "$variab1e" requires explicit package name at line 14.
# load switch statement
Tuesday, 11 August 2009
Devel::Declare
• Lets you change the source Perl is about to compile
• Like a source filter
• But far less fragile.
• Key-hole source filter effect.
Tuesday, 11 August 2009
• When perl parses
catch ($e) { my $foo = …
Using Devel::Declare
• Sees catch as a OP_CONST
Tuesday, 11 August 2009
• When perl parses
catch ($e) { my $foo = …
Using Devel::Declare
• Calls PL_check[OP_CONST]
Tuesday, 11 August 2009
• When perl parses
catch ($e) { my $foo = …
Using Devel::Declare
• Calls PL_check[OP_CONST]
• Devel::Declare hooks this
• And calls back into perl code
Tuesday, 11 August 2009
• When perl parses
catch ($e) { my $foo = …
Using Devel::Declare
• Calls PL_check[OP_CONST]
• Devel::Declare lets us change this into…
catch; { if (my $e = $@) { my $foo = …
Tuesday, 11 August 2009
Using Devel::Declare
• TryCatch doesn’t quite produce that code
• But it shows how things work
• Also uses B::Hooks::OP::{Check,PPaddr} to solve the return problem
Tuesday, 11 August 2009
The return problem
• Simple:
sub foo { eval { return $val }; 1}
• Would be nice if it returned from foo
• vincent++ # Scope::Upper
• unwind HERE, @values;
Tuesday, 11 August 2009
The return problem
• Typing return would be better than unwind
• Install an extra PL_check hook on OP_RETURN
• And install a custom op handler
• Makes return inside try behave like unwind
Tuesday, 11 August 2009
In short, lots of scary XS.
Tuesday, 11 August 2009
use TryCatch;
try { some_call_that_dies();}catch (Error $e) {}catch ($f where {length < 10}) {}catch ($g) {} # otherwise
Tuesday, 11 August 2009
use TryCatch;
try ;{ local $@; eval { some_call_that_dies(); }; $TryCatch::Error = $@; }if ($TryCatch::Error) { if (TryCatch->check_tc('Error')){ my $e = $TryCatch::Error; } elsif (do {local $_ = $TryCatch::Error; length($_) < 10 }) { my $f = $TryCatch::Error; } elsif (1){ my $g = $TryCatch::Error; } # otherwise else { $@ = $TryCatch::Error; die } }
Tuesday, 11 August 2009
hu∙bris |ˈhjuːbrɪs|nounexcessive pride or self-confidence.
• (in Greek tragedy) excessive pride toward or defiance of the gods, leading to nemesis.
DERIVATIVEShu∙bris∙tic |(h)yoōˈbristik| |(h)juˈbrɪstɪk| |-ˈbrɪstɪk| adjective
ORIGIN Greek.
Questions?<[email protected]>
Ash Berlin
Tuesday, 11 August 2009