Jonathan Scott Duff wrote:
> 
> I have only a passing familiarity with exception handling and have
> only used it in "toy" programs.  I'm just trying to wrap my mind
> around the whys and wherefors.  I understand how it works, but not
> why it works the way it does, any references to exception handling
> would be much appreciated.

I'll take a stab at the whys and wherefors here, in an attempt
to help elucidate the terminology and functionality we have been
persuing...

A throw is a kind of non-local goto implemented not with an instuction
counter write, but with stack unwinding, in order to make sure the
non-local goto doesn't corrupt the stack.  This is pretty much required
in a stack-based architecture, because the subroutine call/return
sequence depends on stack containment.

An exception is like a "tag" that keeps track of data about the
non-local goto while the stack is being unwound.

A try + catch is like a statement label; it is a target for the goto,
but it is a dynamic target match, it is based on the exception data
given to the throw, and it happens while stack unwinding.

Of course, during the "dynamic" part of label matching and handling
(during stack unwinding), you must also arrange to handle additional
non-local gotos.  That's why it's hard to code this common usage
with only Perl 5's eval {}, die, and $@, and that's what things like
try, catch, and finally are for.  For example, say you want to write
(using RFC 88 terminology) something like this:

    try     { may throw }    # eval with catch/finally stuff
    catch   { may throw }    # only if exception raised
    finally { may throw };   # whether or not exception raised
                             # including exception from catch!

    # If we get to here try and finally didn't throw, or try
    # threw but catch didn't throw and finally didn't throw.

With Perl 5, you have to say something like this:

    eval { may die };      # try

    my $unwinding = $@;

    if ($unwinding) {

        eval { may die };  # catch

        $unwinding = $@;
        }

    eval { may die };      # finally

    ($unwinding ||= $@) and die $unwinding;

It gets even more complicated when you want conditional catches
where the computation of the predicate may throw, and it gets
much more complicated should you actually want access to an exception
stack while unwinding.  And the purpose of the code quickly becomes
quite unclear if each of the "may throw"s gets replaced by a half-
dozen lines of code.  Those of us who are working on this problem
are looking for some sort of infrastructure to help us out.

Exception *handling* (Perl 5's eval and die) is not the same thing
as exception *objects* (Perl 5's $@), although the two are commonly
used together to implement what might be called "structured error
control" (and other things too, one must remember).  See RFC 88 for
more info: it provides some simple and some not-so-simple examples.

The basic idea behind using exception handling and exception objects
to look after *error* handling (where error more or less means
"assertion failure") is that is a pain to have to check every
function call for a failure return code, especially when most of the
time you would just want to return your own failure code anyway.
So instead of using return codes, functions throw to indicate failure,
and some sort of catch mechanism is explicitly provided to handle
cases where you actually don't want to just return your own failure. 

This technique turns out to fail more "gracefully" too, because if
you forget to check for failure you get failure (including program
termination if no catch gets in the way), not continuation with
possibly bogus state.

One of the problems with this approach is that once people start to
do it, all programs must accept the defaults or otherwise agree to
play along.  Given eval {}, die, and $@, I think we're pretty much
past that point now.

All in all, it turns out that once you allow this non-local stack-
unwinding goto thingy (aka die, or throw), you end up needing
functionality like try (or eval), catch (do block if unwinding),
and finally (do block whether or not unwinding) to conveniently do
the things you're going to want to do with it (like error handling,
for example, and handling exceptions while handling exceptions).

Then, before you know it, you are going to want to make catching
conditional on the current exception, or maybe current exception
stack, and you're going to want all kinds of debugging information
(which you can only get from the *throw*, not from the *catch*,
because then it's too late).  You're also going to find yourself
thinking about what a standard exception should be, and what error
exceptions should be, and why the two aren't necessarily the same.

And then, trying to deal with all this, you're going to end up
having a discussion like this ;-)

Yours, &c, Tony Olekshy

Reply via email to