Exception Handling In Perl

In WhyHatePerl, exceptions were mentioned: "Surely the comparison of 'die' to real exception handling is a joke?". Well, here's an example that demonstrates just how well they match up:

  # aliases to make the code more readable for non-perl programmers
  sub try(&) { eval {$_[0]->()} }
  sub throw($) { die $_[0] }
  sub catch(&) { $_[0]->($@) if $@ }

# The main program try { throw Exception->new("A real exception"); }; catch { print $@->what; };

# The exception class package Exception;

sub new { my $class = shift; bless { msg=>"@_" }, $class; }

sub what { my ($self) = @_; return sprintf "Exception: %s\n", $self->{msg}; }

Most perl programmers shouldn't bother to create the alias functions -- its a bit like "#define BEGIN {" in C. But I think its valid to show that the concepts are equivalent. Without the aliases, you'd see:

  # The main program
  eval {
      die Exception->new("A real exception")
  };
  if ($@) {
      print $@->what;
  }

--DaveWhipp


Setok: I guess this could be acceptable, although looks like a fair bit of magic is involved. What about various exception classes? Can you have separate catch statements for each? What about the finally construct?

I don't see any hairy magic: just the standard idiom of using 'eval' to catch 'die'. For different exception classes, you'll have to use the language's conditional mechanisms: when you think about it, multiple catch blocks are nothing more that a chain of if/elsif tests on an exception's type.

Perl doesn't need a "finally" construct for the same reason that C++ doesn't: it has deterministic object destruction. So you can use the infamous ResourceAcquisitionIsInitialization paradigm. --DaveWhipp

That is assuming you've structured your entire program or at least all your resources as objects and given them proper DESTROY methods and so forth. Besides, finalizers can also run code not related to resource release, such as postconditions (important to verify that cleanup in your current scope happened). Not every exception is thrown from a constructor, and not every exception causes destruction. Yes, it's all just syntax sugar that could be handled with RAII somehow, but hey, all flow control is just syntax sugar for CSP.

The hairy magic comment was referring to the use of magic variables, which I can't make head or tails of, but I assume it works (I'm not too knowledgeable of Perl). I'm not quite convinced you don't need a finally construct. I see it as quite useful if f.ex. opening a file, doing something, and sticking the close in a finally construct. That way it's closed whatever the situation -- whether an error occurs or not. Surely this is even more important with deterministic object destruction? You want to be sure certain objects are destroyed, no matter what happens: just stick a destruction all inside a finally construct. --Setok

    Opened files are closed when the file handle goes out of scope. --JohanLindstrom?

There's only one magic variable involved: $@ -- this tells you what the last exception was (if any). If you want to minimise its use, then we could write:

  eval {
      die Exception->new("A real exception")
  };
  my $exception = $@;

if ($exception) { print $exception->what; }

The deterministic destruction will ensure that all objects are destroyed when they go out of scope. In the file example, the dtor of a FileHandle object will close the file. So when you throw the exception, the file handle goes out of scope, so its dtor is called, so the file gets closed. You simply don't need to worry about it. However, if you really want a finally block, then we can create one:

  sub finally (&) { Finally->new(@_) }

eval { my $final = finally { print "goodbye\n" }; print "hello\n"; die "I'm dead\n"; };

print $@;

package Finally; sub new { my ($class, $code) = @_; bless {code => $code}, $class; } sub DESTROY { my ($self) = @_; $self->{code}->() }

This will behave just like a finally block. You have to assign the block to a variable to tell perl when you want to run it. Hopefully this demonstrates that you don't need a finally block, except as syntactic sugar. --DaveWhipp.


How would you do something like the trivial PythonLanguage function below without having to use two try blocks?:

 def get_val_from_file(myfile):
     try:
         return int(myfile.readline()) # The first line of myfile converted to an integer
     except IOError:
         return -2
     except ValueError?:
         return -1

This works, but returning -2 or -1 isn't very Perlish.-- EarleMartin It's not Pythonic either; this is an example, not useful code.

 use Scalar::Util qw(looks_like_number);

my $file = 'file';

sub get_val_from_file { return -2 unless -f $file && -r $file; # Exists and readable?

open FILE, $file;

# Equivalent to myfile.readline() above. my $val = <FILE>; chomp $val;

# If the first line's a number, return it. return $val if looks_like_number($val);

# If it's not numeric fall back to returning -1. -1; }

Thanks for that, but I was really wondering how it was to be done using the exception code (possibly modified) provided above by Dave Whipp (for that matter, the return values really aren't very Pythonic either, but was done for simplicity) - One can, of course recode the Python version without exceptions (at least mostly - I can't think how to check for file-readability without them), but one's code is longer without them.

The exception structure assumed in the above code doesn't exist in Perl (there is no IO exception); so I'll assume that a function "to_int_or_throw" will throw an exception object whereas an IO error will die with an error message (from Perl). I could now write:

 sub get_val_from_file
 {
     my ($myfile) = @_;

eval { return to_int_or_throw $myfile->getline };

if (!ref $@) # IO Error { return -2; } elsif ($@->isa("ValueError")) { return -1; }

die $@; # rethrow other exceptions }

This is one of those things that I wouldn't write this way (it looks messy, and the design is questionable). Your comments above suggest that you recognise this, but just wanted a simple example. --DaveWhipp.


Is there enough reflection in perl to create an exception system with semantics like Smalltalk, where the block containing the exception can be restarted with different parameters, continued with a value after the method/procedure that raised the exception, aborted, and so on? I'd like to be able to do something more graceful in response to an exception than just ReportAndDie.

I think that the short answer is no. However, with a bit of cooperation from the code that throws the error, we can insert a sort-of continuation into the exception object. As a simple example, here's some code where the exception object is the continuation:

    eval
    {
        print "about to die\n";
        die sub { goto RESUME };
        print "shouldn't be here\n";
      RESUME:
        print "resumed\n";
    };

print "exited the eval\n";

if (ref $@ eq "CODE") { print "resuming\n"; $@->(); } elsif ($@) { die $@; }

print "end\n";

The output of this code is:

    about to die
    exited the eval
    resuming
    resumed
    exited the eval
    end

--DaveWhipp

Umm, I don't think this is what I meant by "resume". The idea would be to do something in the exception handler that fixed whatever problem caused the exception to be raised, then resume execution as if the exception condition had never occurred. In your example above, it would resume at line that prints "shouldn't be here\n". I'm probably misunderstanding you, though. Maybe you could do surgery on the following:

    eval
    {
        print "doing some marvelous code\n";
        if ($bar) {
                print "Bad value for bar!\n";
                die #what goes here?
                }
        print "Everything's ok now!\n";
        $result = $foo/$bar;
        print "doing more marvelous code\n";
    };

We want the output of the finished code to be:

    doing some marvelous code
    Bad value for bar!
    Repairing bar
    Resuming
    Everything's ok now!
    doing more marvelous code

Does this help?

OK, Here's an attempt (still not the cleanest code I've ever written):

    my $foo = 10;
    my $bar = 0;

eval { print "about to divide by bar\n"; unless ($bar) { print "bad value for bar ($bar)\n"; die sub { goto RESUME }; RESUME: } print "value of bar is ok\n"; printf "$foo / $bar = %g\n", $foo/$bar; };

print "exited the eval\n";

if (ref $@ eq "CODE") { print "caught exception; resuming with bar=5\n"; $bar = 5; $@->(); } elsif ($@) { die $@; }

This results in:

    about to divide by bar
    bad value for bar (0)
    exited the eval
    caught exception; resuming with bar=5
    value of bar is ok
    10 / 5 = 2
    exited the eval

This approach does seem to be stressing the limits of Perl though. Lacking real continuations, perl5 is not able to resume in the same stack frame as it died from -- the stacks are actually cleared, so all the variables that we use have to be scoped so that they are visible across all the code. But if you don't use local variables, then it seems to work. If you need locals, then you need to save them explicitly:

    eval {
        my $foo = 10;
        my $bar = 0;

print "about to divide by bar\n"; unless ($bar) { print "bad value for bar ($bar)\n"; die { foo => $foo, bar => $bar, resume => sub { $_=$_[0]; goto RESUME }, }; RESUME: $foo = $_->{foo}; $bar = $_->{bar}; } print "value of bar is ok: foo=$foo, bar=$bar\n"; printf "$foo / $bar = %g\n", $foo/$bar; };

print "exited the eval\n";

if (ref $@ eq "HASH") { print "caught exception; resuming with bar=5\n"; $@->{bar} = 5; $@->{resume}->($@); } elsif ($@) { die $@; }

print "end\n";

--DaveWhipp.


OK, thanks! That looks like as close as we're gonna get.


According to the documentation for the exception mechanism in Error.pm (http://www.perl.com/pub/a/2002/11/14/exception.html?page=2), that module allows the catch block to restart the try block as if there had been no exception (emphasis mine):

  : The BLOCK receives two parameters. The first is the exception being thrown and the second is a scalar reference. If this scalar reference is set on return from the catch block, then the try block continues as if there was no exception.

: If the scalar referenced by the second parameter is not set, and no exceptions are thrown (within the catch block), then the current try block will return with the result from the catch block.

In spite of this claim, I haven't been able to make it work. Can someone help me out?

Here's the code I've tried:

  use Error qw/:try/;

try { print ("Before\n"); Error::Simple->throw(-text => "A Resumeable Failure"); print ("After"); } catch Error::Simple with { my $argCount = @_; my $anError = shift; my $aScalar = shift; my $aScalarCopy = $aScalar; print ("argCount: $argCount\n"); print ("\$anError: \"$anError\", \$aScalar: $aScalarCopy\n"); #$$aScalar = 42; #$aScalar = 42;

#return $aScalar; return; };

Unfortunately, when called, I get the following:
  Before
  argCount: 2
  $anError: "-text at C:\tpm\cgi-bin\TestBlock?.pm line 116.
  ", $aScalar: SCALAR(0x1dbdd08)

Various permutations of the commented-out handler code have not made it work. Am I misunderstanding the doc, or is something else going on?


A Newbie's apologies to good style... Replace:

 my $aScalarCopy = $aScalar;
With:
 $$aScalar = 1;
(it is a scalar reference that needs to be assigned)

That will result in:

 the program dying
(the current??? manual entry being: "BLOCK will be passed two arguments. The first will be the error being thrown. The second is a reference to a scalar variable. If this variable is set by the catch block then, on return from the catch block, try will continue processing as if the catch block was never found" - which is die)

I like that, though I'm not sure that's exactly what the manual meant.


I had the same problem with Error.pm module: poor documentation, no example, and very annoying to debug because the execution flow is not the same as the logical flow (try, catch, finally).


How about this:

   sub try(@) {
        die "Odd number of arguments" if scalar(@_) % 2;

# Extract even numbered elements. my $i = 1; my @keys = grep {$i++ % 2} @_;

my %clauses = @_; die "Duplicate elements" if scalar(keys %clauses) != scalar(@keys);

my $code = $clauses{code}; die "No code to run" unless defined($code); eval { $code->() };

my $error = $@; my $caught = 0;

foreach my $name (@keys) { next if $name eq 'code';

my $run = 0; if ($name eq 'finally') { $run = 1; } elsif (!$caught && UNIVERSAL::isa($error, $name)) { $run = $caught = 1; }

$clauses{$name}->($error) if $run; } }

try(code => sub { die IOError->new if $ARGV[0] eq 'io'; die ValueError?->new if $ARGV[0] eq 'value'; print "No error.\n"; }, IOError => sub { print "IO error!\n"; }, ValueError? => sub { print "Value error!\n"; }, finally => sub { print "Finally!\n"; });

Where IOError and ValueError? descend from the exception class of your choice, with the code of your choice, etc.


See Try::Tiny on TheCpan at http://search.cpan.org/perldoc?Try::Tiny for an excellent discussion of exception handling issues in Perl. It also happens to be a great module that removes much of the pain in Perl exceptions using sane, non-magical Perl code.


CategoryPerl


EditText of this page (last edited May 27, 2010) or FindPage with title or text search