Sign In/My Account | View Cart  
advertisement


Listen Print

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
    }

Perl CookbookPerl Cookbook
By Tom Christiansen & Nathan Torkington
1st Edition August 1998
1-56592-243-3, Order Number: 2433
791 pages, $39.95

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) Exception class itself are used for simple exceptions, for those cases in which one more or less just wants to say throw 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_raise throws 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 in on_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 else and continue with unwind semantics not traditionally associated with else and continue can be confusing, especially when intermixed with local flow-control forms of else and continue (which may be present in any { ... } block), or when an else die $@ is forgotten on a switch that 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 try is 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.

Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9

Next Pagearrow