Tony Olekshy wrote:
> 
> Damian Conway wrote:
> >
> > Actually, I do agree that Perl 6 ought to provide a universal
> > "destructor" mechanism on *any* block. For historical reasons, I
> > suppose it should be C<continue>, though I would much prefer a
> > more generic name, such as C<cleanup>.
> 
> Perl 6 ought to provide universal exit mechanisms for any delimited
> scope, both for the case of normal sequential termination of scope,
> and for the typically special cases of termination by non-local
> flow control due to stack unwinding caused by the raising of an
> exception.  

Hi, it's me again, the guy who won't shut up about exception handling.
I'm trying, in this message and others under this subject prefix, to
collect these end-of-scope matters into a unified discussion that can
help us maximize the signal to noise ratio when dealing with *both*
unwind trapping and end-of-scope situations during our coding efforts.
This should also make it easier for those who are uninterested in the
topic to avoid the discussion.

Consider the following two cases:

    my $f = open $file with_end_of_scope { close $f };

    my $y = f($x) but_if_that_unwinds_use { 0 };

These seem simple enough, so what could go wrong?

Consider the following (where Done() is something that should
be done in end-of-scope order, rather than in $p's GC order):

    my $p = P->new() with_end_of_scope { $p->Done }

So far, so good.  Now what about:

    {    
    my $p = P->new() with_end_of_scope { $p->Done };
    my $q = Q->new() with_end_of_scope { $q->Done };

    ...
    }

Let's see, what should happen after the ...?  Well, $p->Done should
be called, even if the ... started stack-unwinding flow-control.
Maybe $q->Done before $p->Done, but that's a detail.  And then
$q->Done should be called, even if ... or $p->Done starts unwinding.
And if ... or $p->Done or $q->Done starts unwinding, then the
unwinding should be propagated as at the closing curly-brace,
otherwise execution should continue with the next statement in local
flow-control order.

Oops, things just got a little more complicated.

Now before we go any further, a minimalist implementation of all
this is understood; it's called "eval" in Perl 5.  As explained in
RFC 88:

        try { may_throw_1 }
        catch may_throw_2 => { may_throw_3 }
        finally { may_throw_4 }

can be written in Perl 5 like this:

        eval { may_throw_1 };
        my $exception = $@;
        if ($exception) {
            my $test = eval { may_throw_2 };
            $@ and $exception = $@;
            if ( ! $@ and $test ) {
                eval { may_throw_3 };
                $exception = $@;
                }
            }
        eval { may_throw_4 };
        ($exception ||= $@) and die $exception;

but the latter solution has a lower signal to noise ratio, it scales
poorly, and it doesn't implement any sort of exception unwinding stack
concept.

Right, so returning to our $p->Done plus $q->Done example, consider
the case where we want to treat specially one of the possible
exceptions that the ... could be unwinding due to.  I'm switching to
RFC 88 notation here, because if nothing else it is pedantic enough
to make my intent relatively clear (hmm).  Something like:

    my $result;

    foreach ... {

        try {
            my $p = Framework::P->new( ... );
            my $q = Framework::Q->new( ... );

            $result = Server::Foo( $p, $q, ... );
            }

        catch Exception::Framework::Bar {

            $result = Client::Fallback( $p, $q, ... );
            }

        finally { $p and $p->Done(); }
        finally { $q and $q->Done(); }

        last if $result;
        }

What happens if Server::Foo throws an exception and starts
unwinding?  What happens if Client::Fallback does?  What happens
if Q->new() throws an Exception::Framework::Bar exception and
Client::Fallback does *not* throw an exception?  Under what
circumstances does the end of scope propagate an exception
(continue unwinding), and under what circumstances does it continue
with the next statement in local flow-control order?

What happens if Client::Fallback can't open file "Sigma"?  Will
the exception with the message about Sigma get back to the user, 
or will only the most recent message get there (one from $p->Done,
perhaps)?

By now people are asking, if this is so complicated, why not just
keep it simple?  For one thing, it is already the case that the
RFC 88 reference implementation, in Perl 5, does keep the easy
things easy.  The first two examples in this message work as expected
(modulo syntax),  And then, once you look at the effect of a reasonable
set of semantics, you find that less simple constructs do provide
usefull functionality, often helping make the hard things possible
(like really classy error messages, which are in fact somewhat hard
to do).

But why does this matter, since we use exceptions so rarely?
It turns out that there are a number of practioners who have
experimented with using exception handling both for error handling,
and for other purposes, over the years.  PL/I did have OnException
in the early '70s, but I'd probably give credit to CLtL2 for making
unwind-protect a reality.  As a result of all this, there are
actually people who are thinking that the Perl 6 core should signal
"errors" via exceptions, so you can just say

            my $f = open $file;

instead of:

            my $f = open $file or die "can't open $file";

Or as Larry said in his ALS talk, "a completely object-oriented
exception handling, with a simple string-like interface for those
who do not want the power of the full OO system."  See the notes
at http://www.avrasoft.com/perl6/rfc88.htm#CONVERSION for more
information on how this works.

And what does it take to make all this stuff a pragmatic reality?

  * Syntax

    We need a way to give Perl enough hints so that it can DWIM,
    all the while playing well with the rest of the Perl syntax.

  * Semantics

    It must work.

  * Usability

    It must be understandable and functional in practice.

  * Performance

    Those of you who use exceptions to signal errors may not
    appreciate this a priori, since error handling is rarely
    performance-critical, but exceptions are used by people
    for things other than error handling.  The bottom half of
    the exception handling protocol should be light-weight.

So if this is so much trouble, why not just leave it at eval and die?
Actually, that's fine with me, as long as I can still write the
equivalent of the RFC 88 Perl 5 reference implementation in Perl 6.
A modified version of that is what we use in our products anyway.

I'm just saying that if we're really going to build this into the
language, then we should (1) be careful to consider the experiences
of those who have been using this style of software for some time,
not just the opinions of those who are thinking about now neat this
is for the first time, or the first few times, and (2) be careful
to work out the detailed implications of our proposals, in light of
said experiences.

Yours, &c, Tony Olekshy

Reply via email to