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.
It's tempting to make the execution of UNDO contingent upon
whether the block itself was passed during execution, but I'm afraid
that might leave a window in which a variable could already be set,
but subsequent processing might raise an exception before enabling
the rollback in question. So it's probably better to tie it to a
particular variable's state more directly than just by placing the
block at some point after the declaration. In fact, it could be
associated directly with the variable in question at declaration time
via a property:
my $f is undo { close $f } = open $file or die;
Note that the block is truly a closure because it relies on the
lexical scoping of $f. (This form of lexical scoping works in Perl
6 because the name $f is introduced immediately within the statement.
This differs from the Perl 5 approach where the name is not introduced
till the end of the current statement.)
Actually, if the close function defaults to $_, we can say
my $f is undo { close } = open $file;
presuming the managing code is smart enough to pass $f
as a parameter to the closure. Likewise one could attach a POST
block to a variable with:
my $f is post { close } = open $file;
Since properties can be combined, you can set multiple handlers on a variable:
my $f is post { close } is undo { unlink $file } = open ">$file" or die;
There is, however, no catch property to go with the CATCH block.
I suppose we could allow a pre property to set a PRE block on a variable.
RFC:
sub attempt_closure_after_successful_candidate_file_open
{
my ($closure, @fileList) = @_; local (*F);
foreach my $file (@fileList) {
try { open F, $file; } catch { next; }
try { &$closure(*F); } finally { close F; }
return;
}
throw Exception "Can't open any file.",
debug => @fileList . " tried.";
}
Now:
sub attempt_closure_after_successful_candidate_file_open
(&closure, @fileList)
{
foreach my $file (@fileList) {
my $f is post { close }
= try { open $file or die; CATCH { next } }
&closure($f);
return;
}
throw Exception "Can't open any file.",
debug => @fileList . " tried.";
}
Note that the next within the CATCH refers to the loop, not the CATCH
block. It is legal to next out of CATCH blocks, since we won't use
next to fall through switch cases.
However, X::Control exceptions (such as X::NEXT) are a subset
of Exceptions, so
CATCH {
when Exception { ... } # catch any exception
}
will stop returns and loop exits. This could be construed as a feature. When it's considered a bug, you could maybe say something like
CATCH {
when X::Control { die } # propagate control exceptions
when Exception { ... } # catch all others
}
to force such control exceptions to propagate outward. Actually, it would be nice to have a name for non-control exceptions. Then we could say (with a tip of the hat to Maxwell Smart):
CATCH {
when X::Chaos { ... } # catch non-control exceptions
}
|
|
And any control exceptions will then pass unimpeded (since by
default uncaught exceptions are rethrown implicitly by the CATCH).
Fortunately or unfortunately, an explicit default case will not
automatically rethrow control exceptions.
Following are some more examples of how the expression evaluation of
when can be used. The RFC versions sometimes look more concise, but
recall that the "try" is any block in Perl 6, whereas in the
RFC form there would have to be an extra, explicit try block inside
many subroutines, for instance. I'd rather establish a culture in
which it is expected that subroutines handle their own exceptions.
RFC:
try { ... } catch $@->{message} =~ /.../ => { ... }
Now:
try {
...
CATCH {
when $!.message =~ /.../ { ... }
}
}
This works because =~ is considered a boolean operator.
RFC:
catch not &TooSevere => { ... }
Now:
when not &TooSevere { ... }
The unary not is also a boolean operator.
RFC:
try { ... } catch ref $@ =~ /.../ => { ... }
Now:
try { ... CATCH { when $!.ref =~ /.../ { ... } } }
RFC:
try { ... } catch grep { $_->isa("Foo") } @@ => { ... }
Now:
try {
...
CATCH {
when grep { $_.isa(Foo) } @$! { ... }
}
}
I suppose we could also assume grep to be a boolean operator in a scalar context. But that's kind of klunky. If we accept Damian's superposition RFC, it could be written this way:
try {
...
CATCH {
when true any(@$!).isa(Foo) { ... }
}
}
Actually, by the "any" rules of the =~ table, we can just say:
try {
...
CATCH {
when @$! =~ Foo { ... }
}
}
The RFC proposes the following syntax for finalization:
try { my $p = P->new; my $q = Q->new; ... }
finally { $p and $p->Done; }
finally { $q and $q->Done; }
A world of hurt is covered over by that "...", which could move the
finally clauses far, far away from what they're trying to clean up
after. I think the intent is much clearer with POST. And note also
that we avoid the "lexical tunneling" perpetrated by finally:
{
my $p = P.new; POST { $p and $p.Done; }
my $q = Q.new; POST { $q and $q.Done; }
...
}
More concisely, we can say:
{
my $p is post { .Done } = P.new;
my $q is post { .Done } = Q.new;
...
}
RFC:
try { TryToFoo; }
catch { TryToHandle; }
finally { TryToCleanUp; }
catch { throw Exception "Can't cleanly Foo."; }
How I'd write that:
try {
try {
TryToFoo;
POST { TryToCleanUp; }
CATCH { TryToHandle; }
}
CATCH { throw Exception "Can't cleanly Foo."; }
}
That also more clearly indicates to the reader that the final CATCH
governs the inner try completely, rather than just relying on ordering.
RFC:
-
Instances of the actual (non-subclassed)
Exceptionclass itself are used for simple exceptions, for those cases in which one more or less just wants to saythrow Exception "My message.", without a lot of extra tokens, and without getting into higher levels of the taxonomy of exceptions.
die "My message." has much the same effect. I think
fail "My message." will also default similarly, though with
return-or-throw semantics that depend on the caller's use fatal
settings.
RFC (regarding on_raise):
-
Derived classes may override this method to attempt to "handle" an
exception or otherwise manipulate it, just before it is raised. If
on_raisethrows or returns true the exception is raised, otherwise it is not. An exception can be manipulated or replaced and then propagated in modified form simply by re-raising it inon_raise.
Offhand, I don't see this one. Not only does it seem to be making
the $SIG{__DIE__} mistake all over again, it also makes little sense
to me to use "throw" to do something that doesn't throw. A throw
should guarantee termination of control, or you're just going to run
user code that wasn't expected to be run. It'd be like return
suddenly not returning! Let's please use a different method to
generate an unthrown exception. I think a fail method is the
right approach--it terminates the control flow one way or another,
even if just returning the exception as a funny-looking undef.
The on_catch might be a bit more useful.
RFC:
-
...because the authors are of the opinion that overloading
elseandcontinuewith unwind semantics not traditionally associated withelseandcontinuecan be confusing, especially when intermixed with local flow-control forms ofelseandcontinue(which may be present in any{ ... }block), or when anelse die $@is forgotten on aswitchthat needs to re-throw.
CATCH will rethrow by default (unless there is a user-specified default).
RFC:
-
Some perl6-language-error discussions have suggested leaving out
the try altogether, as in simply writing
{ } else { }to indicate non-local flow-control at work. Yikes!The
tryis not for Perl's sake. It's for the developer's sake. It says, watch out, some sort of non-local flow control is going on here. It signals intent to deal with action at a distance (unwinding semantics). It satisfies the first requirement listed under MOTIVATION.


