Structured Exceptions for Perl 5

| 7 Comments

In Features Perl 5 Needs in 2012, I left off one feature that's reasonably easy to add (in comparison) but would provide a huge benefit to Perl 5 now and in the future. Fortunately Mark Fowler reminded me that I forgot it: Perl 5 needs structured core exceptions.

I've written before about the difficulties of parsing unstructured data in a sensible way. That goes for the strings passed to two-argument open, for DBI connection strings, for the text of exception messages, and even the contents of subroutine attributes.

If an exception is only a string, everyone who has to get any relevant information from that string—if that information is even present—has to parse that string. If that information isn't present, adding it is difficult because you might break existing code that already parses it.

I've been experimenting with a way to add a core exception type to Perl 5 in a mostly backwards-compatible way. It's not yet ready to show off, but I can show the language-level interface I'm contemplating.

At a minimum, exception objects can and should include:

  • The string text of the exception we're all used to, say "Attempt to bless into a reference".
  • The name of the file which generated the error, if tracked (and it's almost always tracked)
  • The number of the line which generated the error, if available (and it's almost always available)
  • The type of the exception

The latter is a little more controversial; it requires a lot of people to agree on some sort of classification system for exceptions, like "data access error" or "IO error". It probably requires a system of roles, rather than a singly-rooted hierarchy, because some types of errors overlap. It ought to be visible to user code so users can define their own error types.

(The warnings categorization isn't perfect, but it works in most cases, so making a similar system is indeed possible.)

I've considered also adding a severity, but I don't know that that's broadly useful. It may be a possibility for future extension if necessary.

Modifying the core to produce these exceptions will touch a lot of code, but it's not exceedingly difficult. Nor is making that system available to XS modules difficult. The difficult part is dealing with Perl 5 code which does:

{
    local $@;

    eval { some_code() }

    if (ref  $@) { ... }
    else if ($@) { ... }
    else         { ... }
}

In particular, the open question is whether to make ref() lie, whether to promote exceptions to objects in a lexical scope with a feature enabled, or whether to perform some weird magic and add a feature to extract an exception object from an exception which looks like a string.

(I hope I'm kidding about the last one.)

An exception object would be a normal object and you'd use methods to read its attributes.

I haven't thought at all about how to produce an exception object at the Perl 5 language level. Perhaps a new keyword such as throw (protected by feature) is in order. Overloading die with magic to detect a hash reference won't work, nor will changing its behavior when it receives a list of arguments.

Even with all of these open questions, making this feature work is reasonably straightforward and mostly self contained. It's easy enough to refine over time, but it does offer a measurable amount of improvement at the language level.

I'll keep exploring.

7 Comments

My Kingdom for this feature! I'd be thrilled if I never had to do an "open ... or croak ... ;" again.

Are you not using autodie because of the lack of croak?

I'm not using autodie because it's action at a distance. I can't tell when I'm looking at code whether it's a mistake that it is missing "or die", or if it's been globally enabled somewhere.

I'd rather see the explicate handling in be sure. (On the the other hand, our code base doesn't make a lot of system calls).

I agree having structured exceptions in the core would be nice.

But enabling autodie isn't global; it's lexically scoped. So you can enable it for just one file; or just one sub; or even just in one particular while loop.

I haven't thought about this too much, but what if you introduce a "try" keyword such that these new style exception objects are only thrown (for core errors or other things that "die") when the side handling the error can deal with them. It seems like this would give a clear upgrade path from moving from string based exceptions to object ones. Just a thought...

Maybe! If you have to track "Hey, am I in an active try?" to decide how to throw the exception, things get complicated. Alternately you could always throw the object but turn it into a string only if it gets caught by an eval block.

Is a new keyword really needed for throwing exceptions? How about an exception.pm bundled with core, and auto-loaded such that exception->throw("Error message", %details) "just works"?

As for the ref-ness of $@ this could be handled by a legacy feature - i.e. a feature which is part of the :default, :5.10, .. :5.16 bundles, but not part of :5.18 onwards. This feature would tie the $@ variable so that when it's fetched, it stringifies exceptions but only if $@->isa('exception').

Modern Perl: The Book

cover image for Modern Perl: the book

The best Perl Programmers read Modern Perl: The Book.

sponsored by the How to Make a Smoothie guide

Categories

Pages

About this Entry

This page contains a single entry by chromatic published on September 19, 2012 11:45 AM.

Why Perl 5 Needs Compact, Natively Typed Data was the previous entry in this blog.

Excluding Bot Traffic from Access Logs with Plack Middleware is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.


Powered by the Perl programming language

what is programming?