Day 15 – Something Exceptional

The Perl 6 exception system is currently in development; here is a small example demonstrating a part of the current state:

 use v6;
 
 sub might_die(Real $x) {
     die "negative" if $x < 0;
     $x.sqrt;
 }
 
 for 5, 0, -3, 1+2i -> $n {
     say "The square root of $n is ", might_die($n);
 
     CATCH {
         # CATCH sets $_ to the error object,
         # and then checks the various cases:
         when 'negative' {
             # note that $n is still in scope,
             # since the CATCH block is *inside* the
             # to-be-handled block
             say "Cannot take square root of $n: negative"
         }
         default {
             say "Other error: $_";
         }
     }
 }

This produces the following output under rakudo:

 The square root of 5 is 2.23606797749979
 The square root of 0 is 0
 Cannot take square root of -3: negative
 Other error: Nominal type check failed for parameter '$x'; expected Real but got Complex instead

A few interesting points: the presence of a CATCH block automatically makes the surrounding block catch exceptions. Inside the CATCH block, all lexical variables from the outside are normally accessible, so all the interesting information is available for error processing.

Inside the CATCH block, the error object is available in the $_ variable, on the outside it is available in $!. If an exception is thrown inside a CATCH block, it is not caught — unless there is a second, inner CATCH that handles it.

The insides of a CATCH block typically consists of when clauses, and sometimes a default clause. If any of those matches the error object, the error is considered to be handled. If no clause matches (and no default block is present), the exception is rethrown.

Comparing the output from rakudo to the one that niecza produces for the same code, one can see that the last line differs:

 Other error: Nominal type check failed in binding Real $x in might_die; got Complex, needed Real

This higlights a problem in the current state: The wording of error messages is not yet specified, and thus differs among implementations.

I am working on rectifying that situation, and also throwing interesting types of error objects. In the past week, I have managed to start throwing specific error objects from within the Rakudo compiler. Here is an example:

 $ ./perl6 -e 'try EVAL q[ class A; method { $!x } ]; say "error: $!"; say $!.perl'
 error: Attribute $!x not declared in class A
 X::Attribute::Undeclared.new(
         name => "\$!x",
         package-type => "class",
         package-name => "A", filename => "",
         line => 1,
         column => Any,
         message => "Attribute \$!x not declared in class A"
 )
 # output reformatted for clarity

The string that is passed to EVAL is not a valid Perl 6 program, because it accesses an attribute that wasn’t declared in class A. The exception thrown is of type X::Attribute::Undeclared, and it contains several details: the name of the attribute, the type of package it was missing in (could be class, module, grammar and maybe others), the name of the package, the actual error message and information about the source of the error (line, cfile name (empty because EVAL() operates on a string, not on a file), and column, though column isn’t set to a useful value yet).

X::Attribute::Undeclared inherits from type X::Comp, which is the common superclass for all compile time errors. Once all compile time errors in Rakudo are switched to X::Comp objects, one will be able to check if errors were produced at run time or at compile with code like

 EVAL $some-string;
 CATCH {
     when X::Comp { say 'compile time' }
     default      { say 'run time'     }
 }

The when block smart-matches the error object against the X::Comp type object, which succeeds whenever the error object conforms to that type (so, is of that type or a subclas of X::Comp).

Writing and using new error classes is quite easy:

 class X::PermissionDenied is X::Base {
     has $.reason;
     method message() { "Permission denied: $.reason" };
 }
 # and using it somewhere:
 die X::PermissionDenied.new( reason => "I don't like your nose");

So Perl 6 has a rather flexible error handling mechanism, and libraries and applications can choose to throw error objects with rich information. The plan is to have the Perl 6 compilers throw such easily introspectable error objects too, and at the same time unify their error messages.

Many thanks go to Ian Hague and The Perl Foundation for funding my work on exceptions.

4 thoughts on “Day 15 – Something Exceptional

  1. very nice ! but some questions( about the language):

    I suppose the X class(?) is the generic exception ? will there be type constraints on what can be throw ?, like, it must do ‘X’ ?

    1. X is just a namespace in which exceptions go by default, but it’s not mandatory to put them there.

      There is currently no type constraint for what you can throw. die "some string" and die 404 both work, but the mechanism that catches the exceptions will wrap them into an object of type Exception, which provides the backtrace and other meta data. I’m not sure yet if that exact mechanism is here to stay, but we will continue to allow the user throw exceptions without first writing classes for it.

  2. I get the feeling that it’s considered OK to have “stringly typed” exception handling, for example smart-matching on error messages and the supposed need for standardized messages. Is it not better to standardize exception subclasses and let the message be free-form? It might still be human-friendly to standardize the messages but surely it’s more programming-friendly to match against classes/types?

    1. Perl follows the TIMTOWDI principle; it’s ok for the user to throw string errors; it’s not OK for the compiler. And exception types are what you should be matching against, yes

      But even if we standardize the exception types, we need to unify the wording of the error messages, because there will be non-Perl 6 tools with which we interact. For example if you log the output from a Perl 6 application, and then change the compiler you use under the hood, it would be nice if the wording of the error messages didn’t change, so that the log analyzer doesn’t think a new class of errors occured.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.