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.

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 form catch $foo { ... } could be a test for $foo or a test for $foo{...} (the hash element).>

We now require whitespace before non-subscript block, so this is not much of a problem.

Perl for System AdministrationPerl for System Administration
By David N. Blank-Edelman
July 2000
1-56592-609-9, Order Number: 6099
444 pages, $34.95

RFC:

How can we subclass Exception and control the class namespace? For example, if the core can use any Exception::Foo, where does one connect non-core Exceptions into the taxonomy? Possibly the core exceptions can derive from Exception::CORE, and everyone else can use the Exception::MyPackage convention.

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 Exception and 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 Exception object instance variables not specified to the constructor? For example, tag could 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 to across 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 Builtins

RFC 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_die becomes the one place where a canonical Exception can be generated to encapsulate %@ just before raising an exception, whether or not the use of such canonical Exceptions is controlled by a pragma such as use 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 eval are, "clear $@ and don't unwind unless the user re-dies after the eval". The semantics of try are "unwind after try, unless any raised exception was cleanly and completely handled, in which case clear $@".

In the author's opinion, both eval and try should exist in Perl 6. This would also mean that the legacy of examples of how to use eval in 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, eval is 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 + switch

Some participants in discussions on perl6-language-errors have expressed the opinion that not only should eval be used instead of try, but else should be used instead of multiple catch blocks. They are of the opinion that an else { 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 should undef $@ at the end of *every* successful case block, so that Perl re-raises any $@ still extant at the end of the else.

This RFC allows a switch to be used in a catch { ... } clause, for cases where that approach would minimize redundant code in catch <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, or finally block is entered or exited, and when a conditional catch is 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;
            }

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 an Exception object (which stringifies reasonably), it is now read-only, and it can only be set via die, 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.

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

Next Pagearrow