Apocalypse 4
by Larry Wall
|
Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9
Editor's Note: this Apocalypse is out of date and remains here for historic reasons. See Synopsis 04 for the latest information.
try {} is the new spelling of eval {}, so it can still be used
when self-documentation is desired. It's often redundant, however,
since I think the all-caps CATCH and POST also serve the purpose
of telling the developer to "watch out". I expect that developers
will get used to the notion that many subroutines will end with a
CATCH block. And I'm always in favor of reducing the bracket
count of ordinary code where practical. (That's why the package
declaration has always had a bracketless syntax. I hope to do the
same for classes and modules in Perl 6.)
RFC:
-
The comma or
=in a conditional catch clause is required so the expression can be parsed from the block, in the fashion of Perl 5's parsing of:map<expression, <list>; Without the comma, the formcatch $foo { ... }could be a test for$fooor a test for$foo{...}(the hash element).>
We now require whitespace before non-subscript block, so this is not much of a problem.
|
|
RFC:
-
How can we subclass
Exceptionand control the class namespace? For example, if the core can use anyException::Foo, where does one connect non-coreExceptions into the taxonomy? Possibly the core exceptions can derive fromException::CORE, and everyone else can use theException::MyPackageconvention.
I don't think defining things as core vs non-core is very useful--"core" is not a fundamental type of exception. I do think the standard exception taxonomy should be extensible, so that non-standard exceptions can migrate toward being standard over time. I also think that modules and classes should have their own subpackage in which to store exceptions.
RFC:
-
How can we add new instance variables and methods to classes
derived from
Exceptionand control those namespaces? Perhaps this will be covered by some new Perl 6 object technology. Otherwise, we will need yet another naming scheme convention.
Instance variables and methods in a derived class will not interfere with base classes (except by normal hiding of duplicate method names).
RFC:
-
What should the default values be for
Exceptionobject instance variables not specified to the constructor? For example,tagcould default to file + line number.
Depends on the constructor, I suspect.
RFC:
- What assertions should be placed on the instance variables, if any?
Probably depends on the class.
RFC:
- What should stringification return?
I lean towards just the message, with a different method for more info. But this is somewhat dependent on which representational methods we define for all Objects. And that has not been entirely thunk through.
RFC:
-
Mixed Flow Control
Some of the reference texts, when discussing exception handling, refer to the matter that it may be difficult to implement a
go toacross an unwinding semantics block, as in:try { open F, $f } catch { next; }This matter will have to be referred to the internals experts. It's ok if this functionality is not possible, it can always be simulated with lexical state variables instead.
However, the authors would very much prefer that
gotos across unwinding boundaries would dwim. If that is not possible, hopefully some sort of compile-time warning could be produced.
We can do this with special control exceptions that aren't caught
until it makes sense to catch them. (Where exactly control exceptions
fit in the class hierarchy is still open to debate.) In any event,
there's no problem throwing a control exception from a CATCH,
since any exception thrown in a CATCH or POST would propagate
outside the current try block in any event.
Ordinary goto should work as long as it's leaving the current
try scope. Reentering the try somewhere in the middle via goto
is likely not possible, or even desirable. A failed try should be
re-entered from the top, once things have been cleared up. (If the
try is a loop block, going to the next iteration out of its CATCH
will probably be considered safe, just as if there had been an explicit
try block within the loop. But I could be wrong on that.)
RFC:
-
Use
%@for Errors from BuiltinsRFC 151 proposes a mechanism for consolidating the information provided by of
$@,$!,$?, and$^E. In the opinion of the author of RFC 88, merging$@and$!should not be undertaken, because$@should only be set if an exception is raised.
The RFC appears to give no justification for this last assertion. If we
unify the error variables, die with no arguments can simply raise the
current value of $!, and we stay object oriented all the way down.
Then $! indicates the current error whether or not it's being thrown.
It keeps track of its own state, as to whether it is currently in an
"unclean" state, and refuses to throw away information unless it's clean.
-
%@should be used to hold this fault-hash, based on the following arguments for symmetry.$@ current exception @@ current exception stack %@ current core fault information$@[0] same as $@$@{type} "IO::File::NotFound" $@{message} "can't find file" $@{param} "/foo/bar/baz.dat" $@{child} $? $@{errno} $! $@{os_err} $^E $@{chunk} That chunk thingy in some msgs. $@/2002/01/15/apo4.html Source file name of caller. $@{line} Source line number of caller.%@should not contain a severity or fatality classification.Every call to a core API function should clear
%@if it returns successfully.Internally, Perl can use a simple structured data type to hold the whole canonical
%@. The code that handles reading from%@will construct it out of the internal data on the fly.If
use fatal;is in scope, then just before returning, each core API function should do something like:%@ and internal_die %@;The
internal_diebecomes the one place where a canonicalExceptioncan be generated to encapsulate%@just before raising an exception, whether or not the use of such canonicalExceptions is controlled by a pragma such asuse exceptions;.
This %@ proposal just looks like a bunch of unnecessary complication
to me. A proto-exception object with methods can be just as easily
(and lazily) constructed, and will map straight into a real exception,
unlike this hash. And an object can always be used as a hash to
access parameterless methods such as instance variable accessors.
RFC:
-
eval
The semantics of
evalare, "clear$@and don't unwind unless the user re-dies after theeval". The semantics oftryare "unwind aftertry, unless any raised exception was cleanly and completely handled, in which case clear$@".In the author's opinion, both
evalandtryshould exist in Perl 6. This would also mean that the legacy of examples of how to useevalin Perl will still work.And, of course, we still need
eval $string.Discussions on perl6-language-errors have shown that some would prefer the
eval { ... }form to be removed from Perl 6, because having two exception handling methods in Perl could be confusing to developers. This would in fact be possible, since the same effect can be achieved with:try { } catch { } # Clears $@.my $e;try { ... } catch { $e = $@; }# now process $e instead of $@On the other hand,
evalis a convenient synonym for all that, given that it already works that way.
I don't think the exact semantics of eval {...} are worth
preserving. I think having bare try {...} assume a CATCH { default {} }
will be close enough. Very few Perl 5 programs actually care whether
$@ is set within the eval. Given that and the way we've defined
$!, the translation from Perl 5 to Perl 6 involves simply changing
eval {...} to try {...} and $@ to $! (which lives on as a
"clean" exception after being caught by the try). Perhaps some
attempt can be made to pull an external handler into an internal
CATCH block.
RFC:
-
catch v/s else + switchSome participants in discussions on perl6-language-errors have expressed the opinion that not only should
evalbe used instead oftry, butelseshould be used instead of multiplecatchblocks. They are of the opinion that anelse { switch ... }should be used to handle multiple catch clauses, as in:eval { ... } else { switch ($@) { case $@->isa("Exception::IO") { ... } case $@->my_method { ... } } }This problem with
else { switch ... }is: how should the code implicitly rethrow uncaught exceptions? Many proponents of this model think that uncaught exceptions should not be implicitly rethrown; one suggests that the programmer shouldundef $@at the end of *every* successful case block, so that Perl re-raises any$@still extant at the end of theelse.This RFC allows a
switchto be used in acatch { ... }clause, for cases where that approach would minimize redundant code incatch<expr>{ ... }clauses, but with the mechanism proposed in this RFC, the switch functionality shown above can be written like this, while still maintaining the automatic exception propagation when no cases match:try { ... } catch Exception::IO => { ... } catch $@->my_method => { ... }
The switch construct works fine, because the implied break of each
handled case jumps over the default rethrow supplied by the CATCH.
There's no reason to invent a parallel mechanism, and lots of reason
not to.
RFC:
-
Mechanism Hooks
In the name of extensibility and debugging, there should be hooks for callbacks to be invoked when a
try,catch, orfinallyblock is entered or exited, and when a conditionalcatchis evaluated. The callbacks would be passed information about what is happening in the context they are being called from.In order to scope the effect of the callbacks (rather than making them global), it is proposed that the callbacks be specified as options to the try statement, something like this:
try on_catch_enter => sub { ... }, on_catch_exit => sub { ... }, { ... }The (dynamic, not lexical) scope of these callbacks is from their try down through all trys nested under it (until overridden at a lower level). Nested callbacks should have a way of chaining to callbacks that were in scope when they come into scope, perhaps by including a reference to the outer-scope callback as a parameter to the callback. Basically, they could be kept in "global" variables overridden with
local.
Yuck. I dislike cluttering up the try syntax with what are essentially
temp assignments to dynamically scoped globals. It should be
sufficient to say something like:
{
temp &*on_catch_enter = sub { ... };
temp &*on_catch_exit = sub { ... };
...
}
provided, of course, the implementation is smart enough to look for those hooks when it needs them.
RFC:
-
Mixed-Mode Modules
Authors of modules who wish to provide a public API that respects the current state of
use fatal;if such a mechanism is available, can do so as follows.Internal to their modules, authors can use lexically scoped
use fatal;to explicitly control whether or not they want builtins to raise exceptions to signal errors.Then, if and only if they want to support the other style, and only for public API subroutines, they do something like one of these:
-
Use return internally, now add support for throw at API:
sub Foo { my $err_code = ... ; # real code goes here# Replace the old return $err_code with this:return $err_code unless $FATAL_MODE && $error_code != $ok;throw Error::Code "Couldn't Foo.", code => $err_code; } -
Use throw internally, add support for return at API:
sub Foo { try { # real code goes here, may execute:throw Exception "Couldn't foo.", code => $err_code; } catch !$FATAL_MODE => { return $@->{code}; }return $ok; }
-
Use return internally, now add support for throw at API:
Yow. Too much mechanism. Why not just:
return proto Exception "Couldn't foo.", code => $err_code;
The proto method can implement the standard use fatal semantics
when that is desired by the calling module, and otherwise set things
up so that
Foo() or die;
ends up throwing the proto-exception. (The current proto-exception can
be kept in $! for use in messages, provided it's in thread-local
storage.)
Actually, this is really important to make simple. I'd be in favor of a built-in that clearly says what's going on, regardless of whether it ends in a throw or a return of undef:
fail "Couldn't foo", errno => 2;
Just as an aside, it could be argued that all such "built-ins" are
really methods on an implicit class or object. In this case, the
Exception class...
RFC:
-
$SIG{__DIE__}
The try, catch, and finally clauses localize and undef
$SIG{__DIE__}before entering their blocks. This behavior can be removed if$SIG{__DIE__}is removed.
$SIG{__DIE__} must die. At least, that name must die--we may install
a similar global hook for debugging purposes.
RFC:
-
Legacy
The only changes in respect of Perl 5 behaviour implied by this RFC are that (1)
$@is now always anExceptionobject (which stringifies reasonably), it is now read-only, and it can only be set viadie, and (2) the@@array is now special, and it is now read-only too.
Perhaps $! could be implicitly declared to have a type of Exception.
But I see little reason to make $! readonly by default. All that
does is prevent clever people from doing clever things that we haven't
thought of yet. And it won't stop stupid people from doing stupid things.
In any event, $! is just a reference to an object, and access to the
object will controlled by the class, not by Perl.


