Tony Olekshy writes: : In Apocalypse 4, Larry Wall wrote: : | : | In fact, a C<CATCH> 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.
Thanks. I'm the proof that nearly anyone can be brilliant if they don't have to do it in real-time. :-) : I particularly enjoyed this one: : : CATCH { when @$! =~ Foo { ... } } It was actually in trying to translate that very example that I realized that most of the arrays in the =~ table had an implied any() around them (just as all the lists have an implied any() around them). It hadn't occurred to me before then that Damian's special "array intersection" dwim was the same as any(@foo) =~ any(@bar). From there it was easy to generalize that @foo =~ Class really means any(@foo) =~ Class So, thanks! : 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 ;-? All the LASTs run always. The compiler has to take responsibility one way or another to make sure unelaborated variables still evaluate to undefined. It can, for instance, hoist the allocation to the front, "something like" your example. It's not something that's possible to write an example of in Perl, since you can't separate the allocation from the name introduction in Perl. Maybe with two pseudo-ops indicating the two aspects of "my": { myalloc $p; myalloc $q; myintro $p = P.new; LAST { $p and $p.Done; } foo(); myintro $q = Q.new; LAST { $q and $q.Done; } ... } Alternately, the compiler can keep track of how far the elaboration has proceeded, and compile down to something like: { my $p = P.new; LAST { maybe("$p") and maybe("$p").Done; } foo(); my $q = Q.new; LAST { maybe("$q") and maybe("$q").Done; } ... } where maybe() is smart enough to know whether the variable in question has been allocated. As yet another way, the code searching for the LAST to execute could make sure any unallocated variables get allocated before calling the LAST block, much like { goto X; my $p = 2; X: print $p; } would need to make sure that $p gets created "en passant" if it were not already created before the goto. I expect the first way is the easiest, though perhaps it's not the most efficient way for non-exceptional code. : 2. Consider the following example: : : for my $file ( @files ) { That's obsolete syntax, by the way... : 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" } } : } : } That would be better if you want any exception to go to the next file. Alternately, and perhaps more clearly, you could write: for @files -> $file { my $f = open $file or die; foo($f); CATCH { default { print "foo($f) failed\n"; next } } NEXT { $f.close } } or equivalently for @files -> $file { my $f is next { .close } = open $file or die; foo($f); CATCH { default { print "foo($f) failed\n"; next; } } } : 3. Would the following execute the C<die>? When do I have to worry : about "accidentally" catching control exceptions? : : sub ... : { : return if 1; : fragile(); : CATCH { default { die "Couldn't fragile." } } : } Yes, that would execute C<die> in the current scheme of things. Apo 4 says that you should write CATCH { when X::Kaos { die "Couldn't fragile." } } if that's what you mean, but that's kind of a joke, in at least two senses. I can see several ways out of it, most of which are messy. Perhaps the cleanest solution is to say that CATCH {} only catches non-control exceptions, and CONTROL {} catches control exceptions. Or perhaps CONTROL catches everything, and CATCH has an implied when X::Control { die } at the top. : 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? Yes, something like that. I don't know the exact success formula yet. : 5. What is the order of processing all these special blocks at the : end of their containing block? Is it: : : 1. CONTINUE You mean NEXT. : 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? At the moment, I see this: -2. PRE in order, inherited, no side effects -1. FIRST in order 0. inline code normal flow 1. CATCH, CONTROL singular 2. NEXT, LAST, KEEP, UNDO in reverse order 3. POST in reverse order, inherited, no side effects NEXT has to follow CATCH because the CATCH might invoke C<next>, as in the earlier examples. I don't see an reason to split out all the #2 blocks to separate levels, since they're all "clean up" variants. We do need to figure out whether KEEP and UNDO are simply LAST variants or if they also fire off when NEXT fires off. I can argue it both ways. : 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"? $x must get "1". There can be an explicit way for CATCH or LAST to set the return value, but I think we can't have the final, usually accidental, value of a CATCH or LAST magically becoming the return value. And I don't think the explicit way is with C<return>, since we're talking about the final value of a block that perhaps doesn't handle (or has perhaps already handled!) a C<return>. Might be a good spot for C<yield>, which even in its projected use an an iterator "return" implies that we're providing the return value without wanting to influence the block's native control flow. So maybe that would work. But a real C<yield> may have extra hidden control-flow garbage underneath that would preclude this. Could always use "result" or some such instead. If you replace them all with dies, the die "1" fires off, is caught by the CATCH, which fires off die "2". The die "3" is always fired off on the way out of the block because that's what LAST does. None of the information is thrown away, because die "2" bypasses the C<markclean>. So we end up with a single unclean exception propagating outward whose @$! stack contains all three exceptions. : 7. Is there any particular reason why multiple CATCH blocks can't : simply be queued in some fashion like multiple LAST blocks? We could certainly allow more than one if it were just syntax, but in terms of semantics CATCH blocks are not like LAST blocks. In general, most LAST blocks should not have much interaction with each other. But multiple CATCH blocks would definitely have a lot of interaction where some cases hide other cases, in some non-obvious fashion. If you're going to scatter pieces of a switch statement all over, it's going to be non-obvious, even if the rules are straightforward. It seems better to me to force all the cases into a single CATCH (or CONTROL?), where the hiding of latter cases by earlier ones is at least visually obvious. Larry