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.
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').