Some Apocalypse 4 exception handling questions.
In Apocalypse 4, Larry Wall wrote: | | In fact, a CCATCH of the form: | | CATCH { | when xxx { ... } # 1st case | when yyy { ... } # 2nd case | ... # other cases, maybe a default | } | |means something vaguely like: | | BEGIN { | %MY.catcher = { | given current_exception() - $! { | | when xxx { ... } # 1st case from above | when yyy { ... } # 2nd case from above | ... # other cases, maybe a default | | die;# rethrow $! as implicit default | } | $!.markclean; # handled cleanly, in theory | } | } Beautiful. The synthesis of CATCH, BEGIN blocks, %MY, given, when, break, dwim =~, die, $!, $!.clean, and $!.stack is awe-inspiring. The way proto-exceptions, fail, and use fatal work together is also brilliant. I particularly enjoyed this one: CATCH { when @$! =~ Foo { ... } } I do have a few questions. 1. Does this example: { my $p = P.new; LAST { $p and $p.Done; } foo(); my $q = Q.new; LAST { $q and $q.Done; } ... } effectively get compiled into something like: { my $p; my $q; $p = P.new; LAST { $p and $p.Done; } foo(); $q = Q.new; LAST { $q and $q.Done; } ... } If not, how can we evaluate $q in the LAST block if foo() dies? Or are LASTs not handled by a magic BEGIN mechanism? Or are the LASTs converted into a BEGIN plus some run-time state variable that is only set when the LAST is encountered during execution? Or am I missing the point entirely ;-? 2. Consider the following example: for my $file ( @files ) { my $f is last { close } = open $file or next; foo($f); CATCH { default { print foo($f) failed\n } } } The last and CATCH blocks must be invoked at the end of each time around the for block, no? Or should I be writing: for my $file ( @files ) { try { my $f is last { close } = open $file or next; foo($f); CATCH { default { print foo($f) failed\n } } } } 3. Would the following execute the Cdie? When do I have to worry about accidentally catching control exceptions? sub ... { return if 1; fragile(); CATCH { default { die Couldn't fragile. } } } 4. The test for block exited successfully is C !$! || $!.clean , for the purposes of the block-end handing code, correct? So KEEP is like LAST { if ( !$! || $!.clean ) { ... } } and UNDO is like LAST { unless ( !$! || $!.clean ) { ... } } in which case CATCH is actually like UNDO with an implied given, die, and $!.markclean, except it's handled in a different end- block order, yes? 5. What is the order of processing all these special blocks at the end of their containing block? Is it: 1. CONTINUE 2. CATCH 3. KEEP 4. UNDO 5. LAST 6. POST or some other fixed order, or is there some sort of order-of- encounter interleaving of some of the kinds of blocks? 6. What is the value of my $x = try { 1 CATCH { default { 2 } } LAST { 3 } }; What happens for each permutation of replacing n by die n? 7. Is there any particular reason why multiple CATCH blocks can't simply be queued in some fashion like multiple LAST blocks? Yours, c, Tony Olekshy
Re: Damian Conway's Exegesis 2
Felicitations. Yours, c, Tony Olekshy
Re: End-of-scope actions: Unexpected behavior.
evelopment of a module-based mechanism that does work well. "All this talk about exceptions" is just work toward nailing down the structural details of the -language layer, to provide a reasonable working model of the community perspective to the good folks over at -internals. Yours, c, Tony Olekshy
Re: End-of-scope actions: Background.
Peter Scott wrote: try { die "foo"; } catch { die "bar"; } [...] Surely the first one catches it cleanly since it has a "catch-all" catch clause. That "catch-all" clause throws. In RFC 88 we said, in the Definitions section, Cleanly caught This means the trapping and handling of an exception did not itself raise an exception. So the first one is caught, but it's not cleanly caught. What's the difference? Consider try { ... } catch { foo() }. If foo() throws then try's exception isn't cleanly caught, so unwinding *is* propagated at the end of the try statement. If foo() doesn't throw the exception is cleanly caught, so unwinding is *not* propagated at the end of the try statement. Consider $r = try { $x / $y } catch { $x / $z }; f(); If $y == 0 you want to use $x / $z for $r and then call f(). But wait, *unless* $z == 0 too, in which case you want to unwind, no? So, we propagate after an exception if it is not "cleanly" caught. Just catching it is not enough to terminate propagation, the catching has itself to be "clean", otherwise there's an exception in the catch that hasn't been caught. There's a failure in the failure handling. You want to report that, not catch it. (And if you do want to catch it, just add clauses.) Yours, c, Tony Olekshy
End-of-scope actions: Core exceptions.
Nicholas Clark wrote: my $f = open $file or die "can't open $file"; is troublesome. It doesn't report *why* the file can't be opened. [...] *flexible* exceptions are needed The first version of RFC 88 didn't care what exception objects were, but discussions in the errors mailing list convinced us that we should define a base class for exceptions, to guarantee that certain things are there. Developers can of course define derived classes to extend what exceptions can know/do, and the RFC 88 syntax allows these properties to be used to write conditional catches and fancier unwinding traceback reports. An example of such a derived exception class is included in RFC 88. See the comments on your IO layer problem below. Jarkko Hietaniemi wrote: Nicholas Clark wrote: Jarkko would really like print "Foo\n"; in a void context to behave as print "Foo\n" or die $!; Not just basic I/O but anything 'system': pipe(), system(), opendir(), mkdir(), chdir(), fork(), socket(), and so on. RFC 88 can be used as an exception handling mechanism independent of whether or not the core uses exceptions to signal errors (whether in system calls, math operations, or -gasp- the parser). If the core does use exceptions to signal errors (whether or not controlled by a pragma), then RFC 88 says it punts to RFC 80 on the matter of the details of the derived exception class to be used as the base for core exceptions. RFC 88 only requires that the core exceptions inherit from the base class. RFC 80 is free to define its own class hierarchy (things like Exception::CORE::IO::NotFound), and it's own rules for what information is contained in an exception's message, and how it is formatted. These message-building operations can be overridden for various sections of the core exception class hierarchy. RFC 88 shows a simple way to do pragma-based conversion of error modes at the core API (in which case no exception objects, in the OO sense, need be handled by the bulk of the core), and a simple way to support both exception-based and return-code-based error handling modes based on the dynamic value of a pragma, in module APIs. Personally, I think we should just use exceptions to signal errors, and then (1) there's no need for this pragma-based duality, and (2) you don't have to worry about forgetting to check return codes (thereby mis-handling failures). Nicholas Clark wrote: I'm experimenting with PerlIO layers. If a layer decides it can't honour an open due to a reason not covered by errno, it has no way of reporting this. If exceptions are being used to signal errors, your layer can simply stack an exception that is an instance of a class that inherits from, say, Exception::CORE::Layer, and those exceptions can track whatever sort of error-related data you want. Something like this: package MyLayer; sub OperationFoo { try { code that performs operations using layer below me } catch { throw Exception::CORE::Layer::Me::CantFoo "additional information based on my layer", other = "info", from = "me too", ...; } } Now, if things go bad in the layer below, when somebody gets around to telling the user about the unwinding, $@-show will include messages like the following: ... Exception::CORE::Layer::Me::CantFoo: additional information based on my layer. Exception::CORE::Layer::Low::CantBar: additional information from lower layer. ... You effectively have C map { $_-{errno} } @@ available for all the exceptions raised since the last time an exception was cleanly caught. Nicholas Clark wrote: I think that it would be nice in 5.8 to (optionally on some pragma?) make print, close and a few others in void context croak. It would actually make writing perl scripts easier. You'd know when your disk became full (or you went over quota), albeit in with a messy error. OK, script crashing with an uncaught exception isn't nice, but it's nicer than silently losing data IMHO. RFC 88 says, in support of the notion of using exeptions to signal errors, "How many of us check for IO failures after prints? And if you're writing a simple program you wouldn't want to have to, but you would want the program to shut down after a failure if you don't check." Also, since you're not returning error codes any more, the matter of the void context is moot: failures always throw. Yours, c, Tony Olekshy
Re: End-of-scope actions: Toward a hybrid approach.
"David L. Nicol" wrote: Tony Olekshy wrote: If we take this approach then when you just want to casually say my $f = open $file; always { close $f }; you can. I like that. In addition, [...] How about "later" instead of "always" Because: "later" is a time in the future, but we don't know exactly when, and "always" has an aspect of continuing in time which is distracting (to me.) The problem may be that a dynamic always statement means both "no matter what happens" and "not until later". The static finally clause just means "no matter what happened" (the effect is immediate). For the record, here is the set of keywords for various exception handling clauses that we culled from the errors mailing list, during the RFC process, for the Issues list in RFC 88: raise, always, onException, when, signal, fail, handle, otherwise, unwind, trap, quit, trip, deal, freak, panic, cope, punt, ascend, cough, sustain, overrule, and (of course) longjmp. (The authors were not recorded, to protect the guilty ;-) If we want to choose descriptive names, they should be something like this: except on_dynamic_exception_match always on_dynamic_end_of_block_scope catch on_static_exception_match finally on_static_end_of_block_scope or some other arbitrarily long concatenation of concept labels. In the end, we'll choose a more reasonable set of names, and quickly become used to them. I don't really care what they are, but see RFC 88 for some constraints on the properties they should satisfy. In particular, the keywords should not talk about "failure", because exceptions can in fact be used to signal "success" plus a "get me out of this mess" longjmp. As Piers Cawley wrote during the RFC discussions, "Ah, the 'Bloody hell it worked! That's exceptional' style of programming." ;-) Yours, c, Tony Olekshy
Re: End-of-scope actions: do/eval duality.
Glenn Linderman wrote: Tony Olekshy wrote: Traditionally Perl has had both the "do" and the "eval" block forms, the latter which traps, the former which doesn't. In the perl 5 pocket reference 3rd edition page 63, it claims that $@ is set to the result of an eval or do. How does this impact exception handling tests on $@ to determine if an exception was thrown, if $@ can be set by a do ? OR is that an error in the pocket guide? The following program $@ = 0; eval { 1 }; print '$@: ', $@, "\n"; $@ = 0; eval { die 2 }; print '$@: ', $@; $@ = 0; do { 3 }; print '$@: ', $@, "\n"; $@ = 0; do { die 4 }; print '$@: ', $@; which produces the following results $@: $@: 2 at ... line 2. $@: 0 4 at ... line 4. would suggest that - eval{} always touches $@, setting it to undef or some die's argument, and unwinding (if any) is terminated. It does not set $@ to the result of the eval. - do{} doesn't touch $@ unless the block throws, in which case $@ is some die's argument, and unwinding begins. It does not set $@ to the result of the do. If so, it would appear that the pocket reference has it wrong, and that the traditional operation of "do" is not affected by the details of any try/throw/except/always/catch/finally mechanism that specifies a particular behaviour for $@. From an exception handling perspective, "do" is transparent. Yours, c, Tony Olekshy
Re: End-of-scope actions: Toward a hybrid approach.
Glenn Linderman wrote: Tony Olekshy wrote: If we take this approach then we know exactly what the following code will do. { my $p = P-new(); $p-foo and always { $p-bar }; except Error::IO { $p-baz }; } We also know when the block propagates unwinding; in particular, it doesn't if $p-foo raises an Error::IO exception, unless $p-baz throws, but it does if $p-foo raises some other exception (or if $p-foo doesn't raise an exception and $p-bar throws). By rule 2 above, it would seem that if $p-foo raises an Error:IO exception, that the except block hasn't yet been seen, and therefore the block should propagate unwinding. Ah, yup. ++$bugs{$self} [In RFC 119] The except clause [...] was intended to catch the exceptions of only the statement to which it was attached. I like the conditional syntax you have added to the except clause, to allow catching only specific exceptions. However, in making it a standalone statement, the semantics change significantly. As a clause, the except clause could be attached to the single statement (or block) from which exceptions it was supposed to handle might be propagated. As a statement, however, it seems to have acquired a block level scope [...] I think I know what happened. Back in the 'Assign to magic name-of-function variable instead of "return"' thread, the discussion of the dangling always block came up again. John Porter pointed out that the difficulty of determining the scope of the operation is decreased if the always block is placed inside the block to which it applies, not after it (more below). I used that notion in proposing the hybrid, and inadvertenly broke some of the functionality of RFC 119's dangling except block. The RFC 119 except clause, which was joined to the prior statement, affected only that prior statement. In other words, the except clause and always clause had different relationships to the statement to which they were attached. I'm concerned that I may not understand what you mean by that. I see two alternatives for the dynamic behaviour of the except block, which I'd like to try to explain by considering their "try" dualities. Then perhaps you can let me know which you mean, or if I'm still missing something. Alternative 1: Start with the following code, as written by a developer: { a; b except { c }; d; } Assume, for the sake of discussion, that Perl 6 blocks have try-like semantics, as if the programmer had written: try { a; b except { c }; d; } Now, after statement a (if it did not throw) and *before* b (not after it, like an except statement would), Perl dynamically converts the except block into an equivalent catch clause and unshifts it onto the try statement's pending clause's list. From that run-time point on, it's as if we were executing: try { b; d; } catch { c }; Perl then continues executing statement b. CONSIDERATIONS: c is executed only if b or d throw. Unwinding is propagated if b or d throw, unless c does not throw. Alternative 2: The following code: { a; b except { c }; d; } Is the same as: { a; try { b } catch { c }; d; } CONSIDERATIONS: c is excecuted only if b throws. Unwinding is propagated if b throws, unless c does not throw. Unwinding is also propagated if d throws. So, given those considerations, is one of those alternatives the dual of your concept for the dynamic behaviour of the except block? Now on to the matter of the scope of the dangling block construct. I think I understand the "a except { b }" case: the (deferred) execution of b (iff unwinding) comes into effect just before executing a. In the case of: a and b except { c } Does the deferred execution of c iff unwinding come into effect just before a, or just before b (and only if a is true)? And in the case of: if ( a ) { b } else { c } except { d } Does the deferred execution of d iff unwinding come into effect just before the "if", or just before the "else" (and only if a is false)? Does this work: if ( a ) { b } except { c } else { d } except { e } For reference, here are all the scoping alternatives just discussed, using try's notation: try { a } catch { b } try { a and b } catch { c } a and try { b } catch { c } try { if ( a ) { b } else { c } } catch { d } if ( a ) { b } else { try { c } catch { d } } if ( a ) { try { b } catch { c } } else { try { d } catch { e } } Finally, I'd like to revisit the matter of always. First consider the case where always is a dangling block like except. In such a case, the following could be written: $
Re: End-of-scope actions: Background.
Branden wrote: There's something I didn't quite understand about RFC 88: When I try { die "foo"; } catch { die "bar"; } I die with "bar", right? But what happens if I try { die "foo"; } finally { die "bar"; } I die with "foo" or "bar"? Why is this the right behaviour? Any sample code that shows why this should be done this way and not the other? I both cases, since the "foo" exception is not cleanly caught by either example, unwinding will be propagated at the end of the try statement. The code in catch and finally clauses of any outer try clause (one "containing" the above examples, but not necessary in the same lexical scope) will see the following: $@[0] eq "bar" and $@[1] eq "foo" and $@ = $@[0] with the proviso that the items in the @@ list are actually exception objects that stringify back to the message passed to die. In other words, RFC 88 maintains in @@ a stack of all exceptions raised while in the process of handling and propagating unwinding, with new exceptions unshifted to occupy $@[0] when they occur. This stack is only cleared when exception handling cleanly catches (that is, a catch clause matches and completes without itself raising an exception). Two useful results arise from this technique. 1) In a catch block, print join("\n", @@), "\n"; effectively generates an exception stack traceback. In fact, RFC 88 allows you to say $@-show with that effect, and supports options for including things like showing the Perl stack traceback as at the raising of the first exception (the one in $@[-1]), showing exeption class names, and/or showing other debug information only to developers and log files, but not to end users. That's how it generates messages like this UIM.1234: Can't add a new person to the database. APP.2345: Can't update Company relationship. DBM.3456: Trouble processing SQL UPDATE clause. DBM.4567: Unable to write to Locations table. IOM.5678: Can't open file ".../locations.ndx". IOM.6789: File ".../locations.ndx" not found. which I referred to in http:[EMAIL PROTECTED]/msg05799.html 2) In the test expression in a conditional catch clause, you can operate on the entire @@ stack. For example, try { ... } catch grep { $_-isa("Foo") } @@ = { bar(); } will call bar() only if the try block unwinds *and* any of the exceptions involved in the unwinding are instances of a class that inherits from Foo. Similarly, the following two clauses catch grep { ref $_ =~ /Alpha/ } @@ = { ... } and catch grep { $_ =~ /Beta/ } @@ = { ... } match only if any exception class name matches Alpha, or any exception's message string matches /Beta/, respectively. You can also test other exception object properties with tests of the form catch grep { $_-{tag} eq "XXX.1234" } @@ = { ... } Of course, if you're only interested in the most recent exception, skip the grep operations in these examples and just test $@ directly (which works because of the rule that $@ is always equal to $@[0]). Both of the above results are implemented in the RFC 88 Perl 5 reference implementation (modulo syntax). There are more examples at http://www.avrasoft.com/perl6/rfc88.htm#Examples Yours, c, Tony Olekshy
End-of-scope actions: Background.
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 Ccontinue, though I would much prefer a more generic name, such as Ccleanup. 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
End-of-scope actions: Visibility.
John Porter wrote: Tony Olekshy wrote: I think "always" should be part of an explicit statement, such as "try", not some implied property of block structure introduced by a dangling clause. Why? There's an old engineering joke about instructions that go on and on for pages about how to attach the cover with the special bolts and torque them just so and get how to do the x-ray tests and sign off the job, and somewhere near the end there's a note that says, "before attaching the cover be sure to test the humdinger transpondence." Which of the following sets of instructions is better? These: 1. Skip step one. 2. Kill yourself. 3. Note: if step one was skipped then skip step two. Or these: 1. Warning: if step two is skipped then skip step three. 2. Skip step two. 3. Kill yourself. When instructions are written which modify the way other instructions are to be carried out, the modifying instructions should appear before the modified instructions. For humans reading source code that's "before" in source code order, for CPUs it's in instruction sequence order. That's what "eval" and "try" do. They tell you up front, "Warning, if the following block unwinds, do not propagate your mind out of here as you would normally, instead examine the source code following this block, because trapping is now in effect for the scope of the block." Trapping is special; it's not like traditional flow control because it does not involve any in-flow visible source code signal which indicates to the reader that the implied goto-on-unwind is in effect, unless we explicitly require something like eval or try. It's not like a dangling continue block, because such blocks are triggered by an in-flow visible statement like next. That's why [RFC88] quotes [ESE-1994] to the effect that, "Among the features offered by programming languages to support exception handling are (1) The ability to distinguish the normal control flow from the exception handling flow to make the structure of the program clear [...] Most early programming languages do not provide specific features for exception handling, but rather use the normal constructs to implement it. [...] Obviously this and other ad hoc methods do not satisfy the requirements listed above." On the other hand, a post-hoc "always" says, "Note: if you've already propagated your mind out of here, and you aren't reading this code, you shouldn't have." It's like a roadway sign that says, "The section of road you have just finished driving on may have been slippery if it was wet." It's correct. It's do-able. It's a poor way to do it. Yours, c, Tony Olekshy ESE-1994: The Encyclopedia of Software Engineering, J.J. Marciniak (editor), John Wiley Sons Inc, 1994. ISBN 0-471-54002-1 RFC 88: Omnibus Structured Exception/Error Handling Mechanism, http://www.avrasoft.com/perl6/rfc88.htm
End-of-scope actions: POST blocks.
Nicholas Clark wrote: It makes them far more useful as tidy up things if they are tacked on at runtime, not compile time. If I understand, it is proposed that code like this: { Alpha; POST { Beta }; Gamma; POST { Delta }; Epsilon; } will behave something like this: { my @onEnd = (); try { Alpha; unshift @onEnd, sub { Beta }; Gamma; unshift @onEnd, sub { Delta }; Epsilon; } finally { foreach (@onEnd) { {$_}(); } } } If so, then I have some observations. - It does have in-flow presence, so it doesn't suffer from the problem that "always" has; POST is a statement, not a dangling clause. That fixes my main complaint with RFC 119. On the other hand, now there's nothing at the end of the scope to tell you whether or not have to revisit the whole scope to check if there are any POST clauses before you advance your mind to the next statement. Hmm. - It does solve the dual free problem. Where RFC 88 says: try { my $p = P-new; my $q = Q-new; ... } finally { $p and $p-Done; } finally { $q and $q-Done; } the proposed method could say: my $p = P-new; POST { $p-Done; }; my $q = Q-new; POST { $q-Done; }; ... On the other hand, there is no easy way to specify the order in which $p-Done and $q-Done are invoked, independent of the order in which $p and $q are constructed. Hmm. - The semantics aren't quite the same as "try", which declares exception handling blocks that are active once try's block is entered, not when the declarations are passed in the block. Perhaps both mechanisms should be supported? RFC 88 does suggest that RFC 119 should be considered independent of RFC 88, not as an alternative thereto. - If the contents of a POST block raises an exception, how does it affect other POST blocks? In the example above, if $p-Done throws, then $q-Done should be called, *and* vice versa, *and* unwinding should propagate if either one threw (or anything else did for that matter, unless it was caught without the catching throwing). - What about CATCH blocks (what RFC 88 calls "catch" and RFC 119 calls "except")? What exactly is the interaction between these dynamic POST and CATCH blocks? We will need a detailed semantics, along the lines of http://www.avrasoft.com/perl6/rfc88.htm#Unwinding_Semantics - What about conditional CATCH blocks? What syntax can we use that interacts reasonably well with the rest of Perl? - What's the return value? With RFC 88 you can say: my $r = try { f() } catch { 0 }; What are the syntax and semantics in the CATCH/POST case? Perhaps something like: my $r = do { CATCH { 0 } f() }; Hmm. Yours, c, Tony Olekshy
End-of-scope actions: Reference model 2.0.2.1.
I have extended the RFC 88 Perl 5 reference implementation to support rudimentary POST and CATCH blocks, for which I've used "always" and "except" as the keywords. The new version is http://www.avrasoft.com/perl6/try6-2021.txt Save that file as Try.pm and perl -we "use Try regress = 1" to run the regression tests. All I've done so far is export "always" and "except" subroutines that splice equivalent "finally" and "catch" arguments into try's argument list. This means that, for now, always and except only work in try blocks, and their error messages are reported as those of the generated finally and catch blocks. However, as a result, the following regression tests do work: !always-1 Always stacks a finally. try sub { print "1\n"; always sub { print "3\n"; }; print "2\n"; }; =expect 1 2 3 !except-1 Except stacks a catch. try sub { print "1\n"; except sub { print "2\n"; }; die; print "BAD\n"; }; =expect 1 2 And, it does mean that we have a defined semantics for the purpose of discussion, upon which perhaps we can derive the detailed semantics for Perl 6's exception handling mechanism. Please bear with me. The purpose of this exercise is to make Perl 6 better, not to make it into my version of Java. In fact, I've never programmed in Java. Thank you very much. I learned to appreciate the beauty and elegance of structured exception handling in Scheme and Object Pascal (one of which is a BD language, one of which is not). I'm just trying to figure out how I might be able to make my miniscule contribution to Perl 6 by writing the exception handling FAQ. When I'm explaining { f() always { g() except Error::IO { h() } } } I need to know: does h() get called if f() raised an Error::IO or only if g() does? Yours, c, Tony Olekshy
End-of-scope actions: Error messages.
Johan Vromans wrote: [...] As a result, error messages become utterly useless. I almost never see a Java program that reports "Cannot open file foo". Instead, it reports a java.lang.ioerrorexception and a stracktrace of several pages. Useless if you do not have the source, often even if you have. RFC 88 shows how to solve this problem via its built-in exception unwinding stack, which allows to you generate messages like this when the exception is punted to the user: UIM.1234: Can't add a new person to the database. APP.2345: Can't update Company relationship. DBM.3456: Trouble processing SQL UPDATE clause. DBM.4567: Unable to write to Locations table. IOM.5678: Can't open file ".../locations.ndx". IOM.6789: File ".../locations.ndx" not found. You can also display (or not) the stack trace (there's actually more than one stack trace involved during unwinding, but that's a detail), depending on whether or not the user has developer access (say) or not, and include the exception stack and Perl traceback in the system logs (or not), and include stack traceback and *other* information that should not be presented to the user but should be presented to the developer and the logs (or not), all as appropriate to *your* application. Might that help? Yours, c, Tony Olekshy
End-of-scope actions: do/eval duality.
John Porter wrote: There is no try, there is only do. :-) Nonsense. Traditionally Perl has had both the "do" and the "eval" block forms, the latter which traps, the former which doesn't. "try" is just a slightly souped-up "eval" that better handles the class of problems introduced when exceptions are more commonly used for error handling (by adding some well-defined auxiliary clauses to the statement). Perl has always used an in-flow statement (namely "eval") to signal an unwind-trapping context; are you *sure* you want to change that tradition now? If we work together on this we can make Perl 6's exception handling something worth having worked on. If we throw a bunch of untested ideas together we can only hope they work (at least I hope they work, since Perl has been my favourite language for the last twelve years). Now, shall we? Yours, c, Tony Olekshy
End-of-scope actions: Garbage collection.
Dan Sugalski wrote: I do wish people would get garbage collection and finalization split in their minds. They are two separate things which can, and will, be dealt with separately. For the record: THE GARBAGE COLLECTOR WILL HAVE NOTHING TO DO WITH FINALIZATION, AND NO PERL OBJECT CODE WILL BE CALLED FOR VARIABLES UNDERGOING GARBAGE COLLECTION. Thank you. Thank *you* Dan. I was beginning to wonder if anyone understood. For the record now, GC is none of your business, you're not allowed to know how it works (it's a "black box"). Finalization is handled by try {} finally {}. Finally. Finalization. Get it? Yours, c, Tony Olekshy
Re: End-of-scope actions: Garbage collection.
Dan Sugalski wrote: [...] I wasn't talking about try{}/finally{} stuff. I was talking about DESTROY (or its equivalent) for objects, which unfortunately can't be tied to any one particular place in the code. and, from another thread: I really don't want to guarantee predictable end-of-block cleanup, though, since that means a potentially expensive GC run more often than we might otherwise do. Yes, and I agree with you. I'm just saying that if we're not going to automatically guarantee predictable end-of-block cleanup, then I think we need to provide some way for developers to explicitly specify predictable end-of-block cleanup (using something like an always block or finally clause). Since this explicit operation has to deal with stack unwinding, and therefore with exception trapping, this end-of-block cleanup operation has some eval-like semantics. Therefore, a complete consideration of the matter of end-of-scope includes not just (1) garbage collection, and (2) DESTROY, but also (3) the matter of end-of-scope operations explicitly requested by the developer, in an explicit order which may or may not be related to GC or DESTROY order, and (4) the matter of the interaction between explicit end-of-scope operations and explicit conditional exception handling. Jan Dubois wrote: Could you guys please use "destruction" or "cleanup" as the term for the end-of-scope processing (see e.g. C++). Finalization is used everywhere else to mean: called by GC before the memory is released (see e.g Java/C#). Thanks for the reminder about the ever-present problem of conflicting terminology. I was using "finalization" as a generic label for various kinds of end-of-scope operations. I'm not doing it any more ;-) I think there are three concepts we need labels for, in order to keep the discussion clear. - Code automatically invoked just before an object is finally garbage collected, whenever that actually happens. - Code automatically invoked when an object is finally DESTROYed, whenever that actually happens. - Code automatically invoked when a block scope finally ends, whether because of local flow-control or because of stack unwinding. These are all "end-of-scope" considerations, for various values of "scope". They're all about answering the following question: When the closing curly brace in { ...; my $p = P-new(); ... } is encountered, what happens to the object referred by $p? Yours, c, Tony Olekshy
Re: End-of-scope actions: POST blocks.
the updated reference implementation, I dynamically convert "except"s into "catch"s, which seems to work, so far. Yours, c, Tony Olekshy
Re: End-of-scope actions: POST blocks.
"David L. Nicol" wrote: POST{stuff} is a macro for push (my) @Deferred_stuff, sub {stuff}; # my on first use in a space Since the reference implementation requires try, @Deferred_stuff is actually try's argument list (a bunch of tagged catch and finally blocks). The "my" is provided by try. But in general, yes, if post/always/except is part of Perl 6 then every block will have to keep track of whether or not any such action has been seen during the excecution of the block. Fortunately, it need do nothing special if no such actions have been seen (such as, oh, GC for example). and return(arg) becomes, including implied EndOfBlock return: {shift @Deferred_stuff} while @Deferred_stuff; return(arg) Yes, but you have to keep track of exceptions raised while executing {shift @Deferred_stuff}, so you can propagate them if they occur, and you have to keep track of catch clauses that don't raise exceptions, because they can terminate propagation. For example: my $r = do { except { h() }; f() }; g(); or my $r = try { f() } catch { h() }; g(); should set $r to f() unless f() raises an exception, in which case it should use h() for $r, and whether or not f() raises an exception it should *not* propagate such an exception, it should execute g(). Unless, of course, h() throws, in which case it should propagate that exception, not set $r, and not call g(). On the other hand: my $r = do { always { h() }; f() }; g(); or my $r = try { f() } finally { h() }; g(); should call h() whether or not f() throws; and if f() or h() throw the exception should be propagated, otherwise $r should be set, and g() should be called. Yours, c, Tony Olekshy
End-of-scope actions: Toward a hybrid approach.
I've been thinking about the effect of the minimalist changes I made to the RFC 88 reference implementation, and I don't see any good reason not to support both the static and the dynamic forms of end-of-block-scope actions. Consider the following proposal. 1. Support a try statement (a modified eval) which can have catch and finally clauses, using the semantics of RFC 88. If you want to use catch or finally (instead of always or except) you have to use try. These static catch and finally clauses come into consideration as at when the try is seen, independent of what happens in the block. 2. Support always and except blocks. These constructs may be used without requiring a try before the block. They are dynamic operations which only come into play when they are encountered in the block, in run-time order. 3. Implement always and except by converting them into equivalent dynamically-created catch and finally clauses in the block's actual or implied try statement. Then the semantics of always and except, the details of their effect on exception propagation, and the details of conditional except blocks, are already defined (by RFC 88). If you only want to use always and/or except that's fine, you don't need the try. If you don't want to use conditional catches and/or excepts you don't have to. We get the advantage of TMTOWTDI, while keeping the details understandable and relatively simple to implement by only using one set of semantic rules to cover both cases. If we take this approach then we know exactly what the following code will do. { my $p = P-new(); $p-foo and always { $p-bar }; except Error::IO { $p-baz }; } We also know when the block propagates unwinding; in particular, it doesn't if $p-foo raises an Error::IO exception, unless $p-baz throws, but it does if $p-foo raises some other exception (or if $p-foo doesn't raise an exception and $p-bar throws). If we take this approach then when you just want to casually say my $f = open $file; always { close $f }; you can. I like that. In addition, when you want to carefully say 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; } you can. I like that too. Easy easy, hard possible. The more I think about having both these options available, the better I like it. Both allow me to be lazier (the latter in the case where the exception handling logic is complicated enough that doing it carefully is actually lazier). I get the impression some people think I want verbose code, or some sort of impractial so-called "ivory tower" solution, but I'm really just as lazy as you (probably lazier, but we don't want to debate that here ;-) Yours, c, Tony Olekshy
Re: assign to magic name-of-function variable instead of return
John Porter wrote: [EMAIL PROTECTED] wrote: Hmmm. If there's such an "always" block, I'd like to see it on all blocks, including the continue [1]. But then, it becomes hard to figure out to which block the always belongs That's precisely why these things should be shoved inside rather than dangling off the end. JMHO. I think "always" should be part of an explicit statement, such as "try", not some implied property of block structure introduced by a dangling clause (inside or outside). Once you have an always clause, it has to be invoked during stack unwinding caused by the raising of an exception. This means there is an implied goto-on-exception pending throughout the scope affected by the always clause. This is not like if/else, for, or while, which are all marked up front, and only have explicit variant flow control. It is like eval, but note that eval is marked up front too. Like eval, the beginning of the scope for non-local flow control (such as always and catch) should be explicitly delimited, typically by using a keyword like try. Consider the following two cases: foreach ... { try { ... } catch { ... } finally { ... } } try { foreach ... { ... } } catch { ... } finally { ... } now take out the statement keyword and use magic dangling clauses: foreach ... { { ... } catch { ... } finally { ... } } { foreach ... { ... } } catch { ... } finally { ... } The signal to noise ratio has gone down, no? In fact, the cross product of these cases and the alternatives for dangling always block placement produce these four cases: foreach ... { ... catch { ... } always { ... } } { foreach ... { ... } catch { ... } always { ... } } foreach ... { ... } catch { ... } always { ... } { foreach ... { ... } } catch { ... } always { ... } Now play the problem backwards. Say you run into one of the cross-product constructs in some code. Is it clear to you what the scope and semantics are? Is it clear when always applies to the foreach block, and when it applies to the catch block, and when it applies to the foreach statement? What about the try/finally cases? It's pretty clear, IMHO, that the catch and finally clauses apply to the try statement, simply because try is a statement of which they are part. At that point, the body of the try block and any previous catch or finally blocks that are part of the same try statement are apparent. The previous blocks are critical, because under various circumstances blocks need to be triggered by exceptions raised in previous blocks. From a psychology of programming languages perspective, wrapping the whole mechanism up into a statement per se provides the foundation upon which we can attempt to avoid the conceptual disaster produced by dangling clauses. Mixing up traditional sequential flow-control constructs and non-local stack-unwinding flow-control constructs, without clearly delimiting what you're doing, is (I think) a less than optimal idea. Remember that one of the main uses for catch and always clauses is error handling (as in, close file if opened even if error during processing thereof). I don't like language constructs that obfuscate my attempts to get error handling right (such as they are) because errors in error handling tend to make my code behave relatively poorly. Yours, c, Tony Olekshy PS: since we're completely off subject, can we continue this under http:[EMAIL PROTECTED]/msg05604.html
End-of-scope actions, redux.
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 Ccontinue, though I would much prefer a more generic name, such as Ccleanup. 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. Once you have both types of exit mechanisms, getting the syntax and semantics relatively complete and consistent is not necessarily trivial. Many people put a lot of effort into this very problem in the so-called errors mailing list during the Perl 6 RFC process. One version of the results, with a fairly complete description of the matters of dissent that were raised thereto, is contained in RFC 88, "Omnibus Structured Exception/Error Handling Mechanism", which I co-authored. Using RFC 88's notation, the example being discussed in the parent of this thread would be written something like this: sub read_it { try { my $fh = open @_; $fh } finally { $fh and close $fh } } The existence of the "try" keyword, the choice of other names, and actually important matters such as the lexical scope of $fh, are discussed in RFC 88. More complex constructs that are natural extensions of the concept are also considered, such as the very useful case of multiple finally blocks, along these lines: try { my $p = P-new; my $q = Q-new; ... } finally { $p and $p-Done; } finally { $q and $q-Done; } which invokes $q-Done if $q was allocated, even if $p-Done raises an exception! Please do consider RFC 88 before re-executing the entire debate on this matter again. While many ideas sound good in isolation, gluing the collection of ideas into a coherent whole can be more challenging. And, Larry did mention the try catch catch catch finally construct in his ALS talk. For your reference, here are some RFC 88 links: RFC 88 as HTML http://www.avrasoft.com/perl6/rfc88.htm RFC 88 as Text http://www.avrasoft.com/perl6/rfc88.txt RFC 88 as PODhttp://www.avrasoft.com/perl6/rfc88-pod.txt Perl 5 Try.pmhttp://www.avrasoft.com/perl6/try6-ref5.txt Regression Test http://www.avrasoft.com/perl6/try-tests.htm Yours, c, Tony Olekshy
On RFC 88: Serendipity while throwing a string.
I have been working on the Perl 5 reference implementation of RFC 88 functionality (Try.pm, which is currently available at: http://www.avrasoft.com/perl6/try6-ref5.txt ), and I stumbled across the following result. If you are writing some code, and there is a "throw" subroutine in scope, and there is a package Exception in scope and it has a "throw" subroutine, then Perl 5 can tell the following apart (search for the /!throw-3/ regression test in try6-ref5.txt): throw "A Message"; # Calls the subroutine. throw Exception; # Calls the method. throw "A Foo Message", tag = "ABC.1234"; throw Exception "Foo", tag = "ABC.1234"; I don't know about you, but I think this is cool. Talk about DWIM! Good old Perl. So now, in Try.pm, If you throw a string, you get an Exception anyway: throw "A Message", ...; is now the same as: throw Exception "A Message", ...; You can't say Cthrow $@ to re-raise an exception any more, but you can say $@-throw or use a simple bare Cthrow;! And Cthrow; with @@ == 0 raises a simple Exception! I'll modify RFC 88 to change the throw syntax from: throw := throw E message options ; E := class | object to: throw := throw class message options ; | throw string options ; | throw; and make the appropriate changes to the semantics. And of course, Ctry should accept an additional hook parameter that specifies the class into which to instantiate string throws. Seems obvious in retrospect; we already had class and string, why did E have to be class | object? Ah well, serendipity is like that, I suppose. Yours, c, Tony Olekshy
Re: Structured exception handling should be a core module.
Peter Scott wrote: Tony Olekshy wrote: In fact, not only would I be pleased and honoured to author the Perl 6 core Try.pm module, I'm already working on a Perl 5 standard reference implementation. Peter, I think we should make this approach more clear in RFC 88. I'm not convinced that this can totally be implemented in a module. #- File Try.pm -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# # # Subject:Structured Exception Handling Mechanism for Perl 6 # # Purpose:Perl 5 Reference Implementation of RFC 88 functions. # # This is a self-documenting self-testing implementation, in # Perl 5, of the functionality referred to in Perl 6 RFC 88, # with syntax modified as required by Perl 5. Save in Try.pm. # # Author: Tony Olekshy # Principal Software Architect # Avra Software Lab Inc. # # Copyright: Avra Software Lab Inc, 1999-2000. # #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# use strict; $Try::VERSION = "1.0.1.2"; # 2000-08-26 =head1 ABSTRACT This document assumes a certain familiarity with [RFC-88]. 26 regression tests can be found herein at /#- Regress/. To run the regression tests, use one of: perl -we "use Try regress = 1" perl -we "use Try regress = 2" perl -we "use Try regress = 1, test = 'syntax-1'" perl -we "use Try regress = 2, test = 'syntax-1'" If regress = !0 is specified, regression test are run at import time. If regress = 2, detailed output is generated, otherwise successes report as a single output line. If test = "syntax-1" is specified, only test "syntax-1" is run (see /!syntax-1/ for this example). To run manual tests, perl -w test.pl, where test.pl looks like: use strict; use Try; try sub { throw Exception "Foo" }, catch sub { print "$@[0]\n" }; =head1 DESCRIPTION use Try; exception 'Alarm'; try sub {}; try sub {}, catch sub {}; try sub {}, catch "Foo" =sub {}; try sub {}, catch "Foo", "Bar" = sub {}; try sub {}, catch sub {} = sub {}; try sub {}, finally sub {}; throw Alarm "Can't foo."; =head1 IMPLEMENTATION This reference implementation is written for readability, testability, and distributability. A production version would be optimized for performance. The syntax has been chosen to make the reference implementation simple, not to suggest a preferred syntax for the constructs of RFC 88. The exception, try, catch, and finally "keywords" are exported to the invoker's package. Try and Exception packages are defined, the latter of which contains the base class for exception objects. The current unwind stack is kept in @@; $@[0] is current exception, due to Perl 5 constraints on $@ inside eval. While unwinding, $@ is join("\n",@@)."\n". This implementation uses explicit subs in the syntax for try, catch, and finally clauses, due to Perl 5 parser constraints. Catch clauses that list exception class names take a list of quoted-string class names, due to Perl 5 parser constraints. Lexical scope is not shared between clauses, as per Perl 5. Clauses are closures, not blocks, so @_ == () therein. Clauses are eval'd, therefore $@ eq "" therein. Use $@[0]. Local gotos across unwind-semanitics blocks have not been tested. Dead-code catch clauses are not currently detected by engine. Note that: try {}; finally {}; will ignore the finally! It should be written try {}, finally {}; This would, presumably, be solved by parser extensions in Perl 6. Full exception statement functionality is not yet available. Mechanism hooks are not yet implemented. =head1 ISSUES Get rid of the sub on the sub {} terms. $@ should equal $@[0] inside the clause blocks, not "". =head1 REFERENCE [RFC-88] http://tmtowtdi.perl.org/rfc/88.pod [RFC-80] http://tmtowtdi.perl.org/rfc/80.pod =cut # Interface - package Try; sub exception { _exception; return undef; } sub try { return _try; } sub catch { return (_catch = @_);} sub finally { return (_finally = @_);} sub Hook { return _Hook; } package Exception; sub new { $_[0]-_construct(@_[1..$#_]); } sub throw { $_[0]-_throw(@_[1..$#_]); } package main; @@ = (); # Used for unwind-time exception stack. # Implementation [ Try ] package Try; $Try::Debug = 0;# Set this to include Try subs in tracebac
Re: Structured exception handling should be a core module.
Peter Scott wrote: At 06:48 PM 8/24/00 -0600, Tony Olekshy wrote: I've read 151 a few times, and I don't understand how it can impact the implementation of RFC 88 as a module. Please explain. If $@ and $! are merged, then in code like try { system_call_that_fails(); more_stuff_that_succeeds(); } finally { } does the finally block think there is a current exception or not? $! was set by the failed system call, but nothing died or threw. If $@ is put into $! instead, how does the finally block know that it's not an exception? ! ref($!) ...? I think it is imperative that if Perl has die and eval then it must have a flag that indicates *only* whether or not eval returned via normal local flow control or via non-local unwinding started by a die. Otherwise, I can see no way any quasi-reliable non-local flow control can be extened by writing blocks of local flow-control code. So if open, for example, can set $! without invoking die, then $! and $@ must not be merged. As I read it, 151 would (as currently promulgated) not meet my requirement for the unique nature of a $@-style variable. I don't think overloading ref to pick off true exceptions would make me happy either ;-) I think that RFC 151 should be *merged* into RFC 80. RFC 80 should define a simple set of lower-case system-reserved fields to be used for signalling fault information by the Perl 6 core. RFC 80 should also define a mapping from this simple fault-hash into a proper Exception object (using, oh, say reference to a blessed copy of said fault-hash). Now, hold on to your hat, %@ should the name of this fault-hash... $@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. $@{file}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 %@. 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 Cuse 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 Cuse exceptions;. Yours, c, Tony Olekshy
Re: Structured exception handling should be a core module.
Peter Scott wrote: At 06:06 PM 8/24/00 -0600, Tony Olekshy wrote: In fact, not only would I be pleased and honoured to author the Perl 6 core Try.pm module, I'm already working on a Perl 5 standard reference implementation. Peter, I think we should make this approach more clear in RFC 88. I'm not convinced that this can totally be implemented in a module. Particularly if RFC 151 passes :-) I've read 151 a few times, and I don't understand how it can impact the implementation of RFC 88 as a module. Please explain. Yours, c, Tony Olekshy
Re: On the case for exception-based error handling.
Glenn Linderman wrote: Tony Olekshy wrote: Glenn Linderman wrote: actually wrapping a bunch of code inside a try block affects how that code reacts to die, thus affecting the behavior of the program that previously used die to mean terminate the program. Hang on, this can't be true. To quote from RFC 88: try { may_throw_1 } catch may_throw_2 = { may_throw_3 } finally { may_throw_4 } is exactly the same as (assuming traditional Perl semantics): 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; How can this affect how code reacts to die? It reacts the same as with eval, whether wrapped in a try or not. Say I had: do_something(); now I wrap it in a try: try { do_something(); } this is equivalent to: eval { do_something(); }; my $exception = $@; $exception and die $exception; Which is *exactly* the same as bare do_something(); as per Perl 5. OK, this example clearly demonstrates something. 99% of the catch clauses in the world should be conditional, handling only those specific exceptions that it knows about, and knows it can handle. The "catch-all" (unconditional catch of anything), is error prone; [...] a "catch-all" might bite off _lots_ more than it should chew. Maybe "catch-all"s that do something and then re-throw would be relatively safe. Yes! Only when one knows an awful lot about what's going on in the body of a try is a catch-all that does much more than set a state variable and re-throw possibly safe, because by definition once an exception has raised there are a bunch of things one shouldn't make too many assumptions about. Here is one possibly safe example: try { $y = ( $a * $x * $x + $b * $x + $c ) / $d; } catch { $y = undef; } but this would be even safer: try { $y = ( $a * $x * $x + $b * $x + $c ) / $d; } catch Exception::CORE::Math = { $y = undef; } The only case where catch really should be unconditional (and not just re-throw) is when top-level (some sort of main.pl) code wraps the whole shebang in a try with this type of explicit purpose: try { the_whole_shebang() } catch { pass_to_client($@-show(label = 1, trace = 1)) } Where pass_to_client may be print + exit; Yours, c, Tony Olekshy
Re: On the case for exception-based error handling.
Chaim Frenkel wrote: Tony Olekshy wrote: If no exception is in scope Perl should continue to generate and propagate exceptions (die and $@) as it does now, so we don't break tradition. No, that should be the difference between die and throw. Die is immediately fatal. (i.e. current semantics) throw is new and does the magic. We get no breakage that way. But if die and eval are supposed to do keep doing what they do now, RFC 88 doesn't impact them at all. In fact it depends on what they do now. To quote from RFC 88: Although the following code using the new mechanism: 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; the opportunity for flow-control errors increases. On the other hand, if eval is suddenly supposed to stop catching so-called non-fatal errors, then all hades breaks loose. Trust me. Yours, c, Tony Olekshy
Re: Exception stack: let's use the @@ list.
Peter Scott wrote: At 10:13 AM 8/23/00 -0600, Tony Olekshy wrote: Making throw a method of Exception just means we don't have to say throw Exception-new("Can't foo.", tag = "ABC.1234", ...); and it means throw isn't a new keyword, and that throw $@ can, invoked now as an instance method rather than a constructor, do the right thing. However, a bare Cthrow doesn't make sense now, because it's a method. Like I said before, don't let that stop you if you want to make it do something; you can just make throw a core function as well as a class method. Yes, clearly. The only difference is that an extra comma is required, as in: v throw Exception = "Can't foo.", tag = "ABC.1234"; sub throw { my ($C, $msg, %opts) = @_; die $C-new($msg, %opts) unless ref $C; die $C if $C-isa("Exception"); die new Exception::NotException "...", debug = join(", ", @_); } And, who knows what syntax options will be available with Perl 6? Yours, c, Tony Olekshy
RFC 88 version 2 is available via http.
I've sent this version to the RFC librarian, but it hasn't shown up yet. Until it does, it's available at: Formatted: http://www.avrasoft.com/perl/rfc/rfc88v2.htm POD as text: http://www.avrasoft.com/perl/rfc/rfc88v2.txt This doesn't include Peter's latest changes yet, none of which are show-stoppers. I'll get to their details asap. I've sent this copy in its current state because the widening interest in our little discussion in -erros means people are referring to v1 of 88 in the RFC index, and v2 has major changes relative to that. Yours, c, Tony Olekshy
Re: Exception handling [Was: Re: Things to remove]
Dan Sugalski wrote: Markus Peter wrote: There is no such thing as an ultimately fatal error - it should always be up to the user of a module wether the program should die, but I guess you see that the same and will answer me with "use eval" then ;-) I hope you're speaking from a perl level--a segfault pretty much spells "Game Over"... Dan, FYI ~ Yes, from a Perl level. In general, discussion on -errors have assumed that there will be a class of what we are generically referring to as exceptions that will indeed not behave according to the rules for all other exceptions (such as being able to be caught by eval { ... } ). Certainly segfault and hcffault (that's halt-and-catch-fire fault) would we in that class of exceptions. Other exceptions, such as out-of-memory, might almost be in that class, except when the emergency out-of-memory memory pool comes into play. I haven't though that one through. Then there's exceptions like divide-by-zero and (if Cuse fatal; is in scope) cant-open-file. Clearly, these are catchable. There is currently a running dialogue on whether or not divide-by-zero and other such so-called "fatal" errors should be handled by a separate mechanism from that used for so-called "non-fatal" errors like cant-open-file, where is where you stepped in. Said dialogue will play itself out. Yours, c, Tony Olekshy
Re: Exception handling [Was: Re: Things to remove]
Glenn Linderman wrote: Just to point out that fatal is, indeed, as several people keep saying, truly in the eye of the catcher. That said, none of the currently proposed mechanisms permit "resume from fault" semantics, much less "resume from hardware fault" semantics. Sounds like good RFC fodder to me! Hi, it's me again. Not to be a pain, but RFC 88 does say: retry There has been some discussion on perl6-language-error about the concept of re-entering try blocks on catch, and the possibility of using such a mechanism to replace AUTOLOAD. The author is of the opinion that in order to do this sort of thing properly one should use continuations, which are being discussed elsewhere to this RFC. The intent of this RFC is to provide a simple yet robust exception handling mechanism that is suitable for error handling, not for replacing AUTOLOAD. s/retry/resume/g I'll try to make that more clear in 88v3d1. Yours, c, Tony Olekshy
Re: RFC 88: Possible problem with shared lexical scope.
Peter Scott wrote: Given that even though we know the shared scope could be implemented, the implementors may prefer not to do it. I would therefore reword: We would prefer that the blocks share a common lexical scope in the way that Ccontinue blocks used to; if this is deemed inappropriate, this feature can simply be deleted, and the outer scope can be shared. I added the following to RFC 88 + ISSUES + Lexical Scope: The authors would prefer that try, catch, and finally blocks share the same lexical scope. Yours, c, Tony Olekshy
Re: On the case for exception-based error handling.
Chaim Frenkel wrote: Actually, why not simply unwind the call stack to the routine that has the pragma active. sub foo {use exception; baz()} sub baz { throw "a fit" } sub bar { no exception; foo(); } Yes. The unwind logic would treat a scope with no exception set _as if_ each call were wrapped in at try block. I don't think so. If no exception is in scope Perl should continue to generate and propagate exceptions (die and $@) as it does now, so we don't break tradition. PS ***But it's entirely up to each programmer whether or not they use PS Fatal-checking*** This is the Perl way anyway. Fatal checking, is for core functions. And optional for module authors. Yes. Then Fatal.pm and exception.pm could possibly be consolidated. Yes. I like something like this chaim -- Chaim FrenkelNonlinear Knowledge, Inc. [EMAIL PROTECTED] +1-718-236-0183
Re: RFC 88: What does catch Foo { } do?
Peter Scott wrote: Tony Olekshy wrote: Graham Barr wrote: I am of the opinion that only a class name should follow catch. If someone wants to catch based on an expression they should use catch { if (expr) { } else { # rethrow the error } } Then you will be glad to know that RFC 88, in the not quite ready version two release, allows you do to just that. "Allows" isn't the same as "should be the only way" though. Graham, did you base your opinion on usability, parseability, both, neither? And now for a slightly less frisky answer. The nice thing about the catch expr = block form is not necessarily that you can say things like try { ... } catch $@-{severity} =~ /.../ = { ... } try { ... } catch grep { $_-isa("Foo") } @@ = { ... } try { ... } catch ref $@ =~ /.../ = { ... } but that within the scope of an application's code you can make available utility functions to allow really nice phrasing of rule-based catches (without having to re-raise), such as catch not TooSevere = { ... } catch AnyException("Error::IO") = { ... } my $test = sub { lines of predicate based on $@ }; catch $test("Foo") = { ... } catch $test("Bar") = { ... } ad infinitum Is there actually a good reason not to allow this functionality? Yours, c, Tony Olekshy
RFC 88: Possible problem with shared lexical scope.
Non-shared: my ($p, $q); try { $p = P-new; $q = Q-new; ... } finally { $p and $p-Done; } finally { $q and $q-Done; } Shared: try { my $p = P-new; my $q = Q-new; ... } finally { $p and $p-Done; } finally { $q and $q-Done; } If P-new throws, then the second finally is going to test $q, but it's not "in scope" yet (its my hasn't been seen). Or is it? If it isn't, I'll take shared lexical scoping out and put a note about this in ISSUES instead of the current: If it is not possible to have try, catch, and finally blocks share lexical scope (due, perhaps, to the vagaries of stack unwinding), this feature can simply be deleted, and the outer scope can be shared. Yours, c, Tony Olekshy
Re: RFC 88: Possible problem with shared lexical scope.
Peter Scott wrote: Tony Olekshy wrote: try { fragile(); } catch { my $caught = 1; } finally { $caught and ... } It should work as though each pair of } ... { in between try { and the end of the last finally or catch block isn't there. Storage for lexicals is allocated at compile time, assignment happens at run time. However, my memory as to what the current perl behavior is was faulty; continue blocks do *not* share the lexical scope of their attached loop blocks. So no, what I'm proposing is not the same as anything currently in Perl. But I think it's a good reason anyway (and why shouldn't continue blocks share the same scope? Not so 'lexical', I suppose. Oh well.) RFC 88v2d6 now leaves in shared lexical scope and says the following under ISSUES + Lexical Scope: If it is not possible to have try, catch, and finally blocks share lexical scope (due, perhaps, to the vagaries of stack unwinding), this feature can simply be deleted, and the outer scope can be shared. One possible problem is illustrated by this: try { fragile(); } catch { my $caught = 1; } finally { $caught and ... } If fragile() doesn't throw then finally is going to test $caught even though the my statement was never exccuted. These matters will have to be referred to the internals experts. Yours, c, Tony Olekshy
Draft 3 of RFC 88 version 2.
=head1 TITLE Structured Exception/Error Handling Mechanism =head1 VERSION Maintainer: Tony Olekshy [EMAIL PROTECTED] Date: 19 Aug 2000 Version: 2 (Draft 3) Mailing List: [EMAIL PROTECTED] Number: 88 =head1 DRAFT STATUS This redaction has been modified to reflect Peter Scott's comments through 2000-08-18. Areas of development of this document which are not yet complete as of this draft are annotated with the -- glyph. This version is not intended to be the final redaction of this RFC, as there remains opportunity to enhance the focus and clarity of this document to the benefit of the reviewers hereto. The inclusion of a definitions section for terms like propagate, unwind, exception, and error is under consideration. This RFC needs to better explain the purpose behind factoring Error from Exception, an to more clearly explain the matter of RFC 80. Production editing and spell checking have not been done yet. =head1 ABSTRACT "The Encyclopedia of Software Engineering" [ESE-1994] says (p.847): Inevitably, no matter how carefully a programmer behaves when writing a program and no matter how thoroughly its verification is carried out, errors remain in the program and program excecution may result in a failure. [...] The programming laguage may provide a framework for detecting and then handling faults, so that the program either fails gracefully or continues to work after some remedial action has been taken to recover from the error. Such a linguistic framework is usually called exception handling. This RFC describes a collection of changes and additions to Perl, which together support built-in base classes for Exception and Error objects, and exception and error handling code like this: exception 'Error::DB'; try { throw Error::DB "a message", tag = "ABC.1234", ... ; } catch Error::DB { ... } catch Error::DB, Error:IO { ... } catch $@-{message} =~ /divide by 0/ { ... } catch { ... } finally { ... } Any exceptions that are raised within an enclosing try, catch, or finally block, where the enclosing block can be located anywhere up the subroutine call stack, are trapped and processed according to the semantics described in this RFC. The new built-in Error base class is designed to be used by Perl for raising exceptions for failed operators or functions, but this RFC can be used with the Exception and Error base classes whether or not that happens. Readers who are not familiar with the technique of using exception handling to handle errors should refer to the CONVERSION section of this document first. =head1 DESCRIPTION exception 'Error::App::DB::Foo'; Makes Error::App::DB::Foo into a class that inherits from the built-in Exception class. If the given name matches /::/, something like this happens: @Error::App::DB::Foo::ISA = 'Error::App::DB'; and all non-existent parent classes are automatically created as inheriting from their parent, or Exception in the tail case. If a parent class is found to exist and not inherit from Exception, a run-time error exception is raised. If the given name does not match /::/ (say it's just 'Success'), this happens instead: @Success::ISA = 'Exception'; This means that every exception class isa Exception, even if Exception:: is not used at the beginning of the class name. The exception function can also take optional arguments, along the lines of exception 'Error_DB', isa = "Error::App"; which results in something like @Error_DB::ISA = 'Error::App'; Other options may possibly be given to Cexception to control things like the raise-time stack traceback. throw Error::DB "a message", tag = "ABC.1234", ... ; Throw is both a class and an instance method of the build-in Exception class. The indirect object syntax is used to make the throw imperitive. As a class method, it is syntactic sugar for: die Error::DB-new( message = "a message", tag = "ABC.1234", ...); As an instance method it is syntactic sugar for copying over any values given as arguments, and then effecting Cdie $self. This allows Cthrow $@ to be used to re-raise exceptions. Note that a derived class can override its constructor to preprocess the optional arguments, so that (for example) tags are parsed out of the message, which allows something like this to work for developers who prefer it (such as the author): throw MyError "ABC.1234: A message."; This also illustrates why the message is a required argument to the throw method. It should not have to be more complicated than that to raise an exception of a given type with a given annotation, in common use. One should not have to always add
Re: RFC 88 Exceptions, Errors, and Inheritance.
Peter Scott wrote: Tony Olekshy wrote: That's not what's proposed. The core and other users would use classes derived from Error to raise errors. Other users could even just Error itself. Exception is reserved for exceptions that don't and shouldn't derive from Error. I'm still having a hard time with this. Maybe everyone else sees it and can explain it to me. All I see is, you have a mechanism for implementing non-local flow control. The reason the control flow skipped is surely opaque and irrelevant to the mechanism? What the program decides to do with that exception, likewise. Success and failure are more or less arbitrary interpretations we place on these exceptions. I've taken out the concept of a built-in Error class. It turns out that I really just don't want to have to write throw Exception::Error::App::DB tag = "ABC.1234", message = "Can't write to table $table."; instead of throw Error_DB "ABC.1234: Can't write to table $table."; RFC 88 (now, but not until recently) lets me do that via inheritance, so I now gladly turn all the other built-in error stuff over to RFC 80 ;-) No. I want Error to be able to be extended, over the evolution of Perl 6, to include anything that is deemed suitable for error handling. I don't want any of that to interfer with base Exception functionality. Ok, so now I may have to go and turn off something new in a light-weight class. I can live with that, as long as RFC 80 keeps this in mind. Yours, c, Tony Olekshy
Re: Draft 3 of RFC 88 version 2.
Peter Scott wrote: Dave Rolsky wrote: Tony Olekshy wrote: die If argument isa "Exception", raise it as the new exception and die in the fashion that Perl 5 does. If argument is a string, wrap it in a new Error object, setting the message ivar to the given string, and raise that instead. Actually, the Perl5 die takes a list as its argument and does join '', @_ to it to make the actual error message. If argument is anything else, raise a run-time exception. So this probably shouldn't be the case. This sounds alright; there's something very self-defeating about raising a run-time exception from dying badly, if you see what I mean. Yes! That's why v1 of RFC 88 didn't do that. Thanks, Dave. So the third case goes and the second one becomes, args are stringified and joined on '', etc. It now reads: If passed a single argument that isa "Exception", raise it as the new exception and die in the fashion that Perl 5 does. Otherwise, the arguments are stringified and joined with C'' (as in Perl 5), the resulting string is wrapped up in a new Exception object (setting the message ivar to said string), and the new Exception object is raised. Yours, c, Tony Olekshy.
RFC 88 Exceptions, Errors, and Inheritance.
ather than sounding like I'm sugggesting it's required. Instances of the actual Error class itself are reserved for so-called anonymous exceptions, for those cases in which one more or less just wants to say Cthrow Error "My message.", without a lot of extra tokens, and without getting into higher levels of the taxonomy of exceptions. This is what the user would get by just die-ing. Let's not use 'anonymous'; it makes people think of anonymous arrays, hashes, subroutines, and hence confused. I'll expunge "anonymous". We still have Cdie Error-new; to deal with though. How 'bout if we call such un-subclassed, un-parameterized cases "simple" exceptions. Although this Exception/Error partitioning has not yet been taken advantage of in this RFC, it does provide a good place to help make exception handling and error handling into almost the same thing, without adversely affecting the functionality of either. I think this is unnecessary (and we should shorten the RFC so that more people will read it). Leave the whole Error class out; that's something separate, as I've said. I can make RFC 80 say what you want system exceptions to be. Well, the partitioninig is about to be taken advantage of, in 88v2d3, by explicitly referencing RFC 80 as the "owner" of the Error class, independent of the fact that RFC 88 expects it to inherit from Exception. The built-in Exception class reserves all instance variable and method names matching C/^[_a-z]/. The following instance variables are defined. tag severity message debug object sysmsg trace Why sysmsg? Why wouldn't a core exception use message? Why should someone have to look at both? The message ivar is for the "end user" message. The debug ivar is for additional non-end-user information (for example, in a web app, internal file names might be considered sensitive information, but you would still like to get the name of the file that couldn't be opened into the server log). These ivars are part of Exception. The sysmsg ivar is properly the province of the Error class and RFC 80, which may want to actually introduce a structured value here, since $!, $?, and $^E have to all be accounted for. Technically, the only ones of these that impact the core are: message, and link. The others might be better broken out into another RFC. One RFC for throwing and handling exceptions; another one for what goes in the exceptions. Link is toast, given @@. The tag ivar is also in, because of the namespace managing stuff. The object ivar is required for wrapping non-Exception objects (if we keep that functionality in, otherwise I'd still like to leave it in for Exceptions that "relate-to" and object). And while severity and trace are not strictly required by Exception, it seems reasonable to leave them stubbed in for polymorphism across Errors and Exceptions. Yours, c, Tony Olekshy
Re: Draft 1 of RFC 88 version 2.
Peter Scott wrote: Tony Olekshy wrote: trap { $@-{message} =~ /divide by 0/ } catch { ... } I don't think you need another keyword here. Just support an expression argument to catch and you can do catch $@-{message} =~ /divide by 0/ { ... } If it needs to be more complicated, use 'do'; that should still make it parseable. At the very worst it may need a comma the way map uses it, which we can write as =. But I don't think that'll be needed. I do find that a bit hard to parse, but it probably wouldn't be too bad in practice. Let's go with it, and then maybe you'll let me keep finally instead of continue ;-) I've also added a new ISSUES + Syntax entry, which contains: If Ccatch EXPR { ... } proves to be difficult for Perl to parse, then the form Ctrap { EXPR } catch { ... } is a possible alternative (based on earlier versions of this RFC). Note: I used to favor $_[0] for the exception, but I'm being won over to $@. As long as it doesn't cause problems with external interfaces. Might mention the $_[0] idea in a footnote. Done. finally { ... } Still want this to be "continue"; let's mention both. It will be included in the alternative keywords table I'm working on. Any exceptions that are raised within an enclosing try, catch, trap, or finally block (anywhere up the subroutine call stack) are trapped and processed according to the semantics described in this RFC. Maybe it's just me, but I think of 'up' the stack as being my callers, and 'down' being my callees. I had to laugh. You read the parenthetical as applying to raising, and I wrote it as applying to the enclosing block! I've changed it to: Any exceptions that are raised within an enclosing try, catch, trap, or finally block, where the enclosing block can be located anywhere up the subroutine call stack, are trapped and processed according to the semantics described in this RFC. exception 'Error::DB::Foo'; If the given name matches /::/, something like this happens: @Error::DB::Foo::ISA = 'Error::DB'; If the given name does not match /::/ (say it's just 'DB'), this happens instead: @DB::ISA = 'Exception'; I think you want to s/Error/Exception/ above. Then the rule is simpler. Require all exceptions to be subclasses of 'Exception' for clarity. If I did it that way, then to create an exception error class derived from Exception, I'd have to call it Exception::Error. This would mean testing for Exeption::Error::IO::File::Open. The way it's written above, Cexception 'Error' makes Error a derivative of Exception, but it's name is Error. It's a way of saying that the base class for exception objects is *always* Exception, even if their name doesn't start with Exception:: throw Error::DB = "a message", tag = "ABC.1234", ... ; Suggest dropping the =. Then throw can be implemented as an object method and this is just the indirect object syntax. I forgot. Earlier yesterday I still needed a subroutine, but now that I came with the inheritance trick described above, I don't. It means we can't just throw to reraise, but as you mention below, we can just throw $@; Throw will be a method of the Exception class and of Exception objects in the next draft. Do you really want to leave out the "message =" there? IMHO it looks confusing to tie the class to the message and leave the message attribute name out. So I suggest throw Error::DB (message = "a message", tag = "ABC.1234"); except that my vote is for "code" instead of "tag". We write a lot of simple throws, most of which would look like this with the new mechanism as proposed so far: throw MyDB "ABC.1234: Some message about what went wrong."; Without the techniques and hooks I've described in 88v2d2, I'd have to write the following, which has a much lower signal to noise ratio. throw Exception::MyDB tag = "ABC.1234", message = "Some message about what went wrong."; Even though I want non-local flow-control to stand out with new keywords, I don't what it to obfuscate and overwhelm the source code for the algorithms that use it. And, I really want to encourage people to put an annotating message on *all* the exceptions they raise; I don't want to make it harder by requiring the minimal canonical throw form to always have to contain the message = tokens. A throw with no arguments is syntactic sugar for re-raising the current exception. If there is no current exception, an anonymous Exception is manufactured first. Um, I don't like that. What's an anonymous Exception? What would its attributes be? An anonymous exception is, literally, the result of a non-subclassed, non-parameterized, Exception-New(). Under ISSUES + Exception Base Class, RFC 88 v2d1 says: Default
Re: RFC 63 (v3) Exception handling syntax
Peter Scott wrote: If that were so, even without the ignore() function, I could just say sub Exception::IO::throw { 'do nothing' } and kill it that way. Right. Just like overriding core die. At that point you can change the semantics in such a way as to turn your code into nonsense. I am open to suggestions on whether we should make it impossible for someone that determined to shoot themselves in the foot to do so. My feeling is that someone smart may have a good reason for doing it (in their own code, not in a module given to others), and someone dumb deserves what they get for doing something so blatantly stupid. I agree, we should not make it impossible, but I believe we should make it relatively difficult to do accidentally (much like the forgotten re-throw or function return code checking problems). Yours, c, Tony Olekshy
Re: Towards a reasonable unwinding flow-control semantics.
Executive summary: I no longer want catch blocks to "daisy chain" after a exception is thrown in a catch block. Thanks to everyone who has helped me see the light on this. Peter Scott wrote: At 01:16 AM 8/16/00 -0600, Tony Olekshy wrote: The proposed omnibus Exceptions RFC uses the following three rules to guide it while unwinding through the clauses of a try statement. Forgive me for eliding your explanation, but I find it a little opaque Me too, that's why the subject contains the word "Towards". Let me advance a model which may be simpler. 1. When an exception is thrown perl looks for the enclosing try block; if there is none then program death ensues. 2. If there is an enclosing try block perl goes through the associated catch blocks in order. If the catch criteria succeed (the exception class matches one in a list, or a catch expression evaluates to true, or the catch block catches everything), the catch block is executed. If the catch block throws an exception, it becomes the 'current' exception (with a link to the previous one), otherwise there is no longer a current exception. 3. Whether or not a catch block was executed, the finally block is now executed if there is one. If the finally block throws an exception, it becomes the 'current' exception (with a link to the previous one if there was one). At this point, if there is a current exception, go to step 1. This seems complete and IMHO easily understood. Yes, that's much better. Thanks Peter. Here is a slightly different version which is still slightly more complex, but which allows me to more clearly illustrate the change I am proposing. 1. Whenever an exception is thrown it becomes the current exception (with a link to the previous one if there was one) and Perl looks for an enclosing try/catch/finally block. If there is no such enclosing block then program death ensues, otherwise Perl traps the exception and proceeds as per rule 2. 2. The relevant try block's next associated catch or finally block is processed according to rules 3 and 4. When there are no more blocks use rule 5. 3. If the criteria for a catch succeed (an exception was thrown and either the exception class matches one in a list, or a catch expression evaluates to true without throwing, or the catch block catches all exceptions), the catch block is executed. If the catch block and its test expression do not throw then the current exception is cleared. Otherwise, the exception is trapped and it becomes the 'current' exception (with a link to the previous one). Processing continues with rule 2. [But see below --Tony] 4. When a finally block is encountered it is executed. If the finally block throws an exception it is trapped and it becomes the 'current' exception (with a link to the previous one if there was one). Processing continues with rule 2. 5. After the catch and finally blocks are processed, if there is a current exception then go to step 1. Otherwise continue with the statement after the try statement. My question is: where should processing continue after rule 3? The version shown above produces the problem I described before: try { } except { } = catch { } except { } = catch { } catch { } The potential problem is that if the first catch throws, then the second except will be testing against the $@ from the catch, not the $@ from the try. Is this a problem? Yes, I think it breaks the intuitive model. I do too, so the question is what to do about it. I propose changing rule 3 to add the new paragraph marked with **: 3. If the criteria for a catch succeed (an exception was thrown and either the exception class matches one in a list, or a catch expression evaluates to true without throwing, or the catch block catches all exceptions), the catch block is executed. If the catch block and its test expression do not throw then the current exception is cleared. Otherwise, the exception is trapped and it becomes the 'current' exception (with a link to the previous one). ** If a catch test succeeds (or throws), then whether or not the ** catch block throws, any succeeding catch blocks up to the next ** finally block are skipped. Processing continues with rule 2. This probably means that it should be a syntax error to have any catch clause follow a non-conditional catch, because such a clause could never be executed. throws() outside a try block are caught by the catch blocks of the next enclosing try block. See above. Not quite. Throws in a catch block (not a try block per se) still have to be trapped in order to get to the finally block, if any. I think you probably meant that, I'm just making it explicit. The change I descr
Re: Toward an omnibus Perl 6 Exceptions RFC, v0.1.
Peter Scott wrote: Tony Olekshy wrote: [snip]And the following output was generated: Exception $ = Try::throw('Exception') called from scott2.pm[8]. $ = main::pling('Test') called from scott2.pm[4]. $ = main::bar('Test') called from scott1.pl[1]. $ = main::foo('Test') called from scott1.pl[8]. $ = Try::try(CODE(0xca8830)) called from scott1.pl[9]. $ = Try::try(CODE(0xca2418), 'catch', CODE(0x10b18ac)) called from scott1.pl[8]. If I'm not mistaken, exceptions must bubble up through the call stack as at the point the exception is thrown, so if we capture that stack traceback then, we have all the info. Do you still want the file/line array? Yes, I want to be able to get at it Ccaller-like. You're just dumping it. Ok, but that's just because I was storing it in a string. Instead of going to all the trouble to format up the string, we can copy some of those caller fields into an array (one entry per level) of hashes (one key per value). Like this: $myException-{stackTrace}-[0]-{file} $myException-{stackTrace}-[0]-{line} $myException-{stackTrace}-[0]-{sub} Yours, c, Tony Olekshy
Re: RFC thoughts and guidelines
On this matter, should something like this be a (meta) RFC? =head1 TITLE Guidelines for Developing Changes for Perl 6 (v0.1). =head1 ABSTRACT - If Perl 5 functionality is not broken (at least for some uses), then don't change it unless a critical required core change imples the change in functionality. - If other canonical functionality can be agreed on, add that in a fashion compatible with the existing functionality. - If additional functionality can be done in a (core) module, do it that way unless you have a *really* good argument otherwise. - If you can't do it in a module, try to abstract out the minimal changes to Perl (such as the addition of a well-defined hook) that will allow it to be done in a module. - Make discipline easier to enforce, but don't force any single model of discipline, which may not be appropriate to some uses of Perl. =head1 DESCRIPTION Case Study 1 Even if we agree on a canonical way to handle "error" exceptions (those generated by some sort of "failure" in the program flow), our model may not be suitable for other uses the core functionality provided by eval {}, die, and $@. It's nice to be able to do those other kinds of programs in Perl, too. Case Study 2 When a topic such as Perl's meta-object protocol generates so much heat, perhaps it's best not to over-constrain available solutions. Again, even if we largely agree on a new OO way, our model may not be suitable for other uses the core functionality provided by bless, the - operator, and an extensible meta-object protocol. It's nice to be able to do those other kinds of programs in Perl, too. =head1 IMPLEMENTATION - Enhance the ability to use a common set of pragmas and modules across a group of files, so people can't say all those "uses" are an argument against doing things in modules. - Enhance performance of modules, so people can't use that as an argument against doing things in modules. - Add more hooks to Perl and enhance performance of function and method calls to allow modules to effectively implement additional low-level behaviour. Yours, c, Tony Olekshy
Re: English language basis for throw
"Stephen P. Potter" wrote: I think fail() and handle() are good. Something fail()ed and it was handle()d by an exception. Fail is no good, because exceptions can be used to indicate success. Just because you don't isn't a counter-argument. Exceptions are *not* the same as errors, that's just one semantic mapping. Consider the following case. You've got some complicated algorithm that doesn't normally succeed in finding an answer. When it does find an answer, it can be all over the place, but no matter what, you want to stop the algorithm and return the answer. When you succeed, you want to say throw Exception::MySuccess object = $answer_object; not fail Exception::MySuccess object = $answer_object; and then invoke the whole algorithm under: my $answer; try { my_algorithm; } catch Exception::MySuccess { $answer = $@-object; } If my_algorithm finds the answer then $answer contains the answer, otherwise $answer is undef. If my_algorithm throws anything but an Exception::MySuccess (such as an otherwise uncaught internal Perl error), the statement after the catch is not executed at all. That's why all the other words with negative connotation are bad choices for throw. It's up to the catcher to determine what's good and what's bad, not the thrower. The "classical" uses of try/throw/catch/finally have been around for many years. During that time, many languages have incorported the concepts, and after much heat and little light, the terminology. Could it be that's because, *all* things considered, it is good terminology? Consider "finally" vs. "always". Always? Even if force majeur? Finally simply means, "as the final act of the unwind processing". By the way, this discussion has moved to perl-language-errors, so the good folks here at perl-language-flow can concentrate on finding silly words for other Perl flow-control constructs ;-) Yours, c, Tony Olekshy
Re: Exceptions and Objects
Jonathan Scott Duff wrote: On Sun, Aug 13, 2000 at 07:27:47PM -0700, Peter Scott wrote: An error has text associated with it, but also a bunch of other attributes. So it's a structured data type... where does OOP come into play? 1. It allows you to *extend* the base type with new attributes without having to re-implement all the base attributes. 2. It allows you to *extend* the base type with new methods that can, say, compute predicates based on the object's attributes. Then, you can say things like: if ( $@-isa(MyException) $@-CanFoo ) { ... } 3. Is allows you to *override* base class behaviour, for example, by changing the way stringification works for some classes, or by modifying a constructor to do some sort of logging, for some class of exceptions. They fall naturally into different classes, some of which have subclasses (a File error is-a IO error). This, to me, seems a mere matter of naming. Was it that OOP already had a hierarchical naming system and so it was used? Once the other good reasons for OO exceptions come into play, you may as well take advantage of the serendipity. What does it mean for an exception to have semantics? When an exception is thrown does something special happen whether it's caught or not? $@-CanFoo is an example of semantics that determines whether or not the exception is caught; stringification may be an example of semantics that comes into play when an exception is caught. Yours, c, Tony Olekshy
Re: Exceptions and Objects
Piers Cawley wrote: Tony Olekshy writes: Peter Scott wrote: An exception is an 'error'. That's already a vague concept. I'll say it's vague. There are certainly uses for an exception to trigger non-local success, not an error at all. In fact, there are whole programming techniques based on such uses. Just because most of us usually use exceptions to handle errors, that doesn't mean Perl should not support other uses. Ah, the 'Bloody hell it worked! That's exceptional' style of programming? Yup. But I couldn't have said it better than you just did. Yours, c, Tony (TIMTOWTDI) Olekshy
Re: Exceptions and Objects
Jonathan Scott Duff wrote: On Mon, Aug 14, 2000 at 04:09:41AM -0600, Tony Olekshy wrote: $@-CanFoo is an example of semantics that determines whether or not the exception is caught; stringification may be an example of semantics that comes into play when an exception is caught. Ah, this is why I started asking I guess. Some people were proposing a try/catch like the following: try { } catch SomeException { } catch SomeOtherException { } finally { } which seems to only catch exceptions based on name. Which implies to me that, for exceptions to have useful semantics, they'd have to be rethrown after they're caught. I like the following, but it would also seem that exceptions that aren't handled here would have to be rethrown so that an upstream catch could handle them. try { } catch { # ALL exceptions switch ($@) { case ^_-name eq 'IO' { ... } case ^_-canFoo { ... } throw $@; # No cases matched, rethrow } } finally { } This is why RFC 88 is working on syntax and semantics for: try { ... } except sub { $_[0]-CanFoo } = catch { ... } which *does* unwind if $_[0] can't Foo (or, if $_[0]-CanFoo or the catch clause throws). Yours, c, Tony Olekshy
Re: errors and their keywords and where catch can return toand stuff like that
Evan Howarth wrote: Tony Olekshy wrote: Just be sure to arrange to handle exceptions while handling exceptions. Are you saying that the try-catch proposal automatically handles exceptions thrown in catch and finally blocks? Yes. That's an interesting idea. Java, C++, and Delphi don't do that for you. They expect the programmer to either avoid or correctly handle exceptions in catch and finally blocks. Right, and in practice, it drives me nuts, because if I have a try, a conditional catch, and a finally, I want the finally to happen whether or not the code in the catch throws. How does try-catch handle exceptions in catch and finally blocks. Does it just eat them? The basic syntax considered in RFC 88 is: try { ... throw ... } # try clause except TEST = catch { ... }# 0 or more catch { ... } # 0 or more finally { ... } # 0 or more unwind { ... }; # 0 or 1 The basic semantics are: * The try clause is evaluated. * Each catch clause is invoked, but only if an exception has been raised since the beginning of the try statement, and the catch clause's except TEST is true or is not given. * Each finally clause is invoked whether or not an exception has been raised since the beginning of the try statement. * The unwind clause, if any, is invoked if an exception has been raised since the beginning of the try statement, and it has not been cleanly caught. * After processing all clauses, try unwinds (dies) iff any exception wasn't cleanly caught. An exception is considered to be "cleanly caught" if it was in the try clause, and it triggered a catch clause, and no catch or finally clause raised an exception. There are some other semantics that come into play with multiple catch clauses, for detailed information see RFC 88. Yours, c, Tony Olekshy
Re: errors and their keywords and where catch can return toandstuff like that
Peter Scott wrote: Tony Olekshy wrote: When you want the first one, use try + catch. When you want the second one, use eval, then manipulate $@. Just be sure to arrange to handle exceptions while handling exceptions. Erk, people shouldn't have to use such radically different syntax for a tiny semantical change. They don't have to. They can just use a try with an empty catch, which is what Peter and I wanted in the first place. I was only pointing out that there are two camps involved here, one of which likes eval semantics and the other which likes try semantics. If we leave eval alone in Perl 6, and add try, then everyone should be happy, and there will be one less potential problem converting Perl 5 code. We may need not even need to add try to the Perl guts (RFC 88 as implemented in Perl 5 is a module that adds try, so perhaps all we need is a core module and possibly an efficiency hook or two). Everyone seems to have started thinking about the implication of inheritance in exception classes at the same time. Whatever the default behavior is, we can easily change it on a global basis (using Chaim Frenkel's sub name here); sub Exception::uncaught_handler { maybe rethrow, maybe not } Or a per-class basis: sub Exception::IO::uncaught_handler { ... } Or a per-object basis (gross as I find this): my $e = Exception::SQL-new(...); $e-uncaught_handler = sub { ... } throw $e; And the dispatcher would look first in the object, then in the class for uncaught_handler. Ideas about exceptions and exception handling are easy. Getting a suite of ideas to cooperate in practice can be more difficult. For example, with try { throw Exception1-New; } catch { throw Exception2-New; } does the uncaught_handler of Exception1 get called? One could say, "no, because it was caught", or "yes, because it wasn't fully caught, because the catch didn't complete". And by the way, what's supposed to happen when an uncaught_handler throws? I'd like to see some working code excercising uncaught_handler, with documentation and regression tests I can study. One could base such an implementation in the code referenced in RFC 88. I think it's cool how this process is converging :-) Yes, but let's not get carried away. We have to try to come up with a clean set of basic concepts that work well in the easy easy, hard possible sense. If we want to use unwinding with exception objects as the basis for Perl's failure handling, then we *certainly* don't want a fragile exception handling mechanism compounding the failure for us. What is the cost of an accidentally lost throw when you are using exception handling for error handling? As Henry Spencer used to say, "then all bets are off". If you don't like food-for-thought digressions, skip this paragraph. Say you and I are playing a game of catch (the verb). We can throw a football, or a baseball, or a frisbee, all using the same basic throwing and catching mechanism. We can teach that mechanism how to handle different classes of throwable-catachables, but we can't teach it to throw or catch 16T blocks. And in no case is the object being thrown allowed to reach out and change my hand into a foot. Yours, c, Tony Olekshy
Re: errors and their keywords and where catch can return toandst uff like that
Peter Scott wrote: If anyone suggests that try { } catch Exception::Foo, Exception::Bar { ... } catch { exception thrown here causes it to start going through catch blocks again } then I'm afraid I'm going to have to turn to drink. Agreed. However, consider this: try { may_throw_1; } catch { may_throw_2; } catch { may_throw_3; } finally { may_throw_4; } Under what conditions should the second catch clause be invoked? Under what conditions should the above unwind after finally (vs continuing the normal order of execution? RFC 88 currently goes into the second catch if the first catch throws, but perhaps it should go straight to the finally if any catch throws. Or maybe not. Consider this case (in pseudo-syntax): my $o = SomeClass-New; try { f ($o); } catch $@-ImpliesFooCleanup { $o-AttemptFooCleanup; } catch $@-ImpliesBarCleanup { $o-AttemptBarCleanup; } finally { $o-Done; } If you would want to attempt bar cleanup even if attempting foo cleanup fails (throws), then RFC 88 gets it right (the syntax is different, and the unwind stack is used to prevent the second exception from hiding the first one, but it works). We either have to disallow multiple and conditional catches and move everything to a catch-all block and do all the flow control manually (for which we already have eval {}), or we have to specify the semantics for multiple conditional catches. Yours, c, Tony Olekshy
Re: RFC 92 (v1) Extensible Meta-Object Protocol
"Randal L. Schwartz" wrote: Tony Olekshy wrote: Perl should be modified so that if C$ISA::Search (or equivalent) Do you mean "$YOUR_PACKAGE::ISA::Search" which is in the package "YOUR_PACKAGE::ISA"? This would be the first time (to my knowledge) that something would be in an unrelated package. Remember... there is no necessary correlation between $A and $A::B as of yet. Your proposal would breach that. Oops, sorry. That's what we do (aka get away with) in our little generator module, but I can see that it doesn't scale well. Presuming the rest of the idea is deemed sound enough to implement (:-), I'd strongly suggest the variable name be something like $ISA_SEARCH or something like that, to keep it in the same package. Hmm, what about %ISA{Search}? Then, if we identify other meta-object protocol hooks (or parameters) we could specify them via other entries in %ISA. Meanwhile, @ISA could stay the same. I'm not too worried about the details at this point. However, what I would like is for a subset of Perl 6 to almost indistinct from Perl 5: additions are ok, extensions are ok, but I'd rather not rewrite all the Perl code in the world just to handle someone's idea of what Perl should have been if they were in charge ;-) I also would rather not be forced to use someone else's inextensible definition of OO that is too simple or too complex for my needs. Perl's got a good start on a very minimalist OO protocol. Let's make the minimal visible fixes we can to that, the maximal fixes to making it efficient, and the necessary fixes to make it more easily and compatabily extensible, so that multiple OO protocols can interoperate in the same program. Yours, c, Tony Olekshy
Re: RFC 63 (v2) Exception handling syntax
is the key, and the exceptions are just what you happen to be handling. We should be able to do a pretty good job of putting these two perspectives together into binocular vision (-; | ;-) Yours, c, Tony Olekshy
Re: RFC 95 (v1) Object Classes
Andy ~ Since you didn't mention it in your references, you may want to check out RFC 92, Extensible Meta-Object Protocol -- Method Search at http://tmtowtdi.perl.org/rfc/92.pod RFC 92 considers an existing Perl 5 module we have that allows us to write code like the following, and it considers how to make Perl 6 better support this kind of extensibility via modules (rather than via new core functionality forced on us all). package MyClass; use Prothos::Class ISA = ParentClass; ivar Foo = Public,Write = Private; ivar Bar = Private, Default = "Hello, World"; ivar Baz = Protected, Default = {}; # Hash ivar. method HelloWorld = Public, sub { my ($I, %A) = @_; $I-Baz(Name = $A{Name}); # Ivar accessor. print $I-Baz("Name"), " says \"", $I-Bar, ".\n"; }; method Cogitate = Private, sub { ... }; method OverrideMe = Protected, Bind = Virtual, sub { ... }; Our little class generator does the things we need, without taxing our conceptual notion of what Perl OO looks like. As far as I can tell, RFC 95 doesn't do the things we need, while taxing our notion of what Perl OO looks like. So I guess you can chalk up my comment to be "skeptical", at least for now. Yours, c, Tony Olekshy
Re: RFC 80 (v2) Exception objects and classes for builtins
Peter Scott wrote, in RFC 80 (v2): =item id Unique numeric identifier, assigned by perl developers. I'm loath to bother everyone with this, but to me the id of an object should be unique to each *instance* of the class. If we had my $e = Exception-New(id = "ABC.1234"); my $f = Exception-New(id = "ABC.1234"); then $e and $f would have the same id, but be different objects. In RFC 96 I've proposed called this attribute the "tag", in the sense of those little paper labels tied to merchandise with a string or the little embossed metal plate attached to a motor or to a pressure relief valve (that's what mechanical engineers call them). If we really want an id attribute, I think it should be "$self" or ++$foo for some base-module lexically scoped $foo. I can't think of any practial use for it right now, but that certainly doesn't mean there can't be any. Line number exception was thrown at. File exception was thrown in. Should this be line thrown at or line constructed at? Does anyone care? =item data[(userdata)] User data, arbitrarily complex. If the user knew the underlying object implementation, of course they could stick in attributes with any names they wanted; but nothing should rely on that, so this hook is provided. Something like this is probably a good idea, but as noted in RFC 96, "How to extend ivars and control namespace?". Stringifying the object itself will yield the Cmessage attribute. Or perhaps a formatted combination of a subset of the attributes, as in RFC 96? A Cfacility attribute was suggested to indicate what part of perl is throwing the exception: IMO that is covered in the exception class. Agreed. Yours, c, Tony Olekshy
Re: RFC 80 (v1): Exception objects and classes for builtins
I've moved this from perl6-language to perl6-language-flow. Tony Olekshy wrote: With the approach proposed in RFC 88 (Structured Exception Handling Mechanism), you could write that as: try { } catch { switch ($_[0]-name) { case IO { ... } case Socket { ... } } } Graham Barr wrote: the error are objects, so you need to allow for inheritance. I was just trying to point out that RFC 88 uses try {} catch {} instead of try {} otherwise {}, and that the current error comes into the catch block via @_ (as in RFC 63), so one doesn't need a "global". Sometimes you want to collect all the catching into one clause (if, say, there was lots of common code and little varying code). In other cases, you want a seperate clause for each exception (if, say, there is little common code, then the seperate clauses handle the switch for you, which is more DWIM). That's why RFC 88 allows you any combination of these operations, as in: try { } except isa = "Foo" = catch { ... } except isa = "Bar" = catch { ... } except else = catch { ... } Again, the differences between this and RFC 63's approach are, in this case, only syntactic. Yours, c, Tony Olekshy
Re: RFC 80 (v1): Exception objects and classes for builtins
I've moved this from perl6-language to perl6-language-flow. Graham Barr wrote: eval { # fragile code } else { # catch ALL exceptions switch ($@) { case __-isa('IO') { ... } case __-isa('Socket') { ... } else { ... } } } continue { # code always executed (ie finally) } Chaim Frenkel wrote: Nice. Hmm. The eval was commented to indicate fragile code, which is implied if the keyword try is used. The else was commented to indicate a catch, instead of saying catch, and the continue was commented to indicate a finally, instead of saying finally. There does seem to me to be some benefit to the clarity of the RFC 88 approach, which supports both: try { } except isa = 'IO' = catch { } except isa = 'Socket' = catch { } except else= catch { } finally { } and: try { } catch { switch ($_[0]) { case __-isa('IO') { ... } case __-isa('Socket') { ... } else { ... } } } finally { } Yours, c, Tony Olekshy
Re: RFC 80 (v1): Exception objects and classes for builtins
Peter Scott wrote: John Porter wrote: Which makes me think that it would be nice if the continue block could come before the catch block(s). I get where you're going with this but it breaks the paradigm too much. Now you need a 'finally' block again. Sometimes you want before, sometimes after, as in: try { open(*F, "foo") or throw "Can't open foo."; print F ...; } finally { close F or throw "Can't close foo."; } unwind { attempt_to_log_error_message($_[0]); } which can also be written as: try { try { open(*F, "foo") or throw "Can't open foo."; print F ...; } finally { close F or throw "Can't close foo."; } } catch { attempt_to_log_error_message($_[0]); throw; } The exception handling mechanism considered in RFC 88 has both pre-finally and post-finally exception trapping clauses, named catch and unwind. The basic syntax considered in RFC 88 is: try { ... throw ... } # try clause except TEST = catch { ... }# 0 or more catch { ... } # 0 or more finally { ... } # 0 or more unwind { ... }; # 0 or 1 The basic semantics are: * The try clause is evaluated. * Each catch clause is invoked, but only if an exception has been raised since the beginning of the try statement, and the catch clause's except TEST is true or is not given. * Each finally clause is invoked whether or not an exception has been raised since the beginning of the try statement. * The unwind clause, if any, is invoked if an exception has been raised since the beginning of the try statement, and it has not been cleanly caught. * After processing all clauses, try unwinds (dies) iff any exception wasn't cleanly caught. An exception is considered to be "cleanly caught" if it was in the try clause, and it triggered a catch clause, and no catch or finally clause raised an exception. Yours, c, Tony Olekshy
Re: RFC 80 (v1): Exception objects and classes for builtins
Chaim Frenkel wrote: [ ... ] for me polymorphism is action-at-distance of the worst stripe. Its the cheap and dirty way of doing OO. [...] I see very little reason to have two methods with different signatures. Method signatures and polymorphism are orthogonal. The latter refers to different classes having differing implementations of a method of the same name (often via inheritance and overriding) and/or name + signature. Say you had a base class for exception objects that defines a method that returns whether or not Foo is true for that class. Say you derive a descendent class from that base class via inheritance, and then extended the derived class to add a new instance variable. Say you then, in your derived class, override the method that returns whether or not Foo is true to take your new instance variable into account. With polymorphism, one doesn't have to know whether one has an instance of the base class or an instance of the derived class (using kludgery such as isa), one can just say $o-IsFoo and both cases work as per Fooing. Signatures don't need to enter into it. For more information see http://www.cyberdyne-object-sys.com/oofaq2/body/typing.htm#S2.1 Yours, c, Tony Olekshy
Re: RFC 85 (v1) All perl generated errors should have a
Chaim Frenkel wrote: [stuff about exception numbering] Hmm, I thought I saw another exception RFC pass by. Yup, RFC 88, Tony Olekshy [EMAIL PROTECTED] Could you two folks get together and hash this out. RFC 88 goes to some trouble to seperate exception handling from exception objects. It doesn't care how exception objects are numbered, labelled, or titled; it only cares how they stringify and isa. However, RFC 88 does say: If Try's notions of the seperation of exception handling from the implementation of exception objects gains credence, there will need to be one or more RFCs on the matter of the built-in or core-module Exception class sanctioned by Perl 6. RFC 88 does provide a minimalist exception object base class, but it doesn't consider the manner in which this is extended, such as for numbering, labelling, or source-code tracking. It also doesn't consider the class hierarchy for exception objects. I will however be delighted to participate in the discussion of RFCs related to Perl 6's canonical exception class. We should probably move to [EMAIL PROTECTED] Yours, c, Tony Olekshy