What if we implemented something like the following?

    exception 'MyException';

        This is like use Exception 'MyException'; but since
        the Exception base class needs to be available to the
        Perl guts, it probably won't be a module per se.

    try { ... throw MyException->new(...) ... }

        If the try block throws then unwinding starts (try's
        internal unwinding state gets set to 1 and the raised
        exception gets saved in $@), otherwise it doesn't.

    catch { ... }

        Invoked if unwinding.  If the catch block throws unwinding
        continues, otherwise unwinding stops (either way, the
        finally still gets processed).  In the catch block, $@ is
        the current exception.

    catch "ClassName" { ... }

        Invoked if unwinding and $@->isa("ClassName").  If a
        comma-seperated list of names is given, the catch is
        invoked if grep { $@->isa($_) } ( ... );  If the catch
        block doesn't throw then unwinding stops.

    except { ... } => catch { ... }

        Invoked if unwinding and the except block returns true.
        In the except block, $@ is the current exception.
        If the except or catch blocks throw, unwinding continues.
        If both complete without throwing, unwinding stops.
        This is basically an "if" with unwinding semantics.

    finally { ... }

        Invoked whether or not unwinding.  If the finally block
        throws then unwinding starts or continues, if finally
        doesn't throw then the unwinding state is not changed.
        Inside the finally block, $@ is the last raised exception
        if unwinding, and undef if not unwinding.

    catch { ... }

        Invoked if unwinding.  This is a post-finally catch, but
        otherwise it can have all the syntax of other catches.
        If the catch block doesn't throw then unwinding stops.

    There can be any combination of catches and finallys; don't
    use combinations you don't like.

    When all the clauses are complete, if the unwinding state is
    set then the last raised exception is re-thrown, otherwise
    execution continues with the next statement.

This is (I believe) very similar to RFC 63 syntax, other than the
addition of "except", and fact that throw is not a constructor (you
must pass throw a constructed exception, if that's what you want),
so people can still say things like throw "Can't open $file.";
The semantics for clause flow control is also different from that
in RFC 63, but only in the more complex cases.

Now say throw wraps up it's argument in an Exception object unless
it already isa Exception object.  $@ or equivalent contains the
current exception (but a linked list of previous exceptions raised
since unwinding started is kept via a _link attribute in the
Exception objects, which you can ignore except in the case of the
"any" method described below, or if you want to generate a formatted
unwind stack to show the user).

Exception objects would have "internal" attributes named with a
leading "_", and would also reserve the namespace for attributes
beginning with a lower-case letter, which is would use for fields
like "tag", "message", "severity", and those containing various
kinds of throw-time debugging information.  /^[_a-z]/ method names
would also be reserved.

Then, we could write things like the following and more, simply
by adding methods to the Exception class, or by writing whatever
other tests you want in the except clause.

    except { $@->{ivar} eq "value" }

        The catch is invoked if the given ivar of the last raised
        exception has the given value.  For example, $@->{severity}
        eq "Fail".

    except { $@->{ivar} =~ /value/ }

        The catch is invoked if the given ivar matches the given
        value.  For example, $@->{severity} =~ /^(Fail|Fatal)$/.
        Because of $@'s stringification of $@->{message}, you can
        also say things like except { $@ =~ /division by 0/ }.

    except { $@->tag("Foo") }

        The catch is invoked if $@->{tag} eq (ref $@) . ".Foo",
        which handles the case of tag namespace control (assuming
        that if the Exception constuctor is given tag => "Foo" it
        does $self->{tag} = (ref $self) .".". $arg{tag};  Or some-
        thing like that.

    except { ref $@ =~ /.../ }

        The catch is invoked if the exception's class name matches
        the given RE!

    except { $@->any( sub { ... } ) }

        The given closure is invoked for each object on the unwind
        stack, by following the _link fields in Exception objects,
        passing each object in as $_[0].  As soon as an object is
        found for which the closure returns true, the catch is
        invoked.  If no object satisfies the closure, the catch is
        not invoked.

    except { $@->isa("Foo") and $@->CanBar }

        This handles exception object predicates added by extending
        the Exception base class.

If I'm not mistaken, that lets me do everything RFC 88 does, it is
more regular and directly extensible, and it still leaves the option
for unwind stack formatting via the internal links.

Note that the syntax considered above can't be done as a Perl 5
module, but it can be simulated if the catch blocks are preceeded
by "sub", $@ is replaced by $_, and some []s and commas are added
(so we can still write regression tests for the proposal in Perl 5).
Also note that "except" can be removed if catch can take two blocks
as arguments, but that may be getting to hard for humans to parse.

Yours, &c, Tony Olekshy

Reply via email to