3

I'm playing with resumable exceptions. In this example, I try to numify something that doesn't numify. I catch that and attempt to give the $value variable an appropirate value then resume execution:

try {
    my $m = 'Hello';
    my $value;
    $value = +$m;
    put "Outside value is 「{$value.^name}」";
    CATCH {
        when X::Str::Numeric {
            put "「$m」 isn't a number!";
            put "Inside value is 「{$value.^name}」";
            $value = 0;
            put "Inside value is now 「$value.」";
            .resume;
            }
        default {
            put "Unhandled type 「{.^name}」";
            }
        }
    put "End of the block";
    }

put "Got to the end.";

The CATCH block can see the lexical scope it is in, a resuming picks up where it left off. I expected that I'd be able to change $value and have the rest of the block use that value, but outside of the CATCH the value becomes a Failure:

「Hello」 isn't a number!
Inside value is 「Any」
Inside value is now 「0.」
Outside value is 「Failure」
End of the block
Got to the end.

What's up?

jjmerelo
  • 22,578
  • 8
  • 40
  • 86
brian d foy
  • 129,424
  • 31
  • 207
  • 592
  • 2
    I suspect it's an order of evaluation effect: the exception seems to interrupt the `$value = +$m` statement, with the assignment only happening after the `CATCH` block has been executed, overwriting the 0 – Christoph Apr 08 '17 at 07:08

1 Answers1

5

Inside of a try block, use fatal takes effect, to cause lazy exceptions returned from method or sub calls to throw immediately. Outside the lexical scope of a try block, note that:

my $value = +$m;

Would result in a Failure being assigned to $value. The try turns it into something more like:

my $value = force-failure(+$m);

Which you could imagine being defined as something like:

sub force-failure(Mu \f) { f.sink if f ~~ Failure; f }

(I'm hand-waving because the compiler spits out the code to do this inline and with a few optimizations).

In the case under consideration, the .sink triggers the exception to the thrown. The CATCH block runs. The .resume indicates that we do not wish to unwind the call stack as would normally happen with a CATCH block, so execution continues inside of force-failure, which then returns f - the Failure. This all happens prior to the assignment in the mainline code to $value; the Failure is therefore assigned, overwriting the value given by the CATCH block.

Unfortunately, you can't escape this with //= because that does the test before running the RHS (which is what we usually want it to do). However, it is possible to do:

my $numified = +$m;
my $value //= $numified;

Of course, this is all a bit of a contrived example, since the normal idiom would be to not have a try block at all, and to write it as:

my $value = +$m // 0;

Thus taking advantage of the Failure. In general, resumable exceptions need a good amount of care, because in many cases code will not be written expecting a resumption to take place. It turns out that the code generated for fatalizing a Failure is one such piece.

Jonathan Worthington
  • 29,104
  • 2
  • 97
  • 136
  • Is this stuff documented? I only find vague references. – brian d foy Apr 08 '17 at 11:43
  • 2
    Hopefully, when someone finds lacking or unclear documentation, that person creates an issue at https://github.com/perl6/doc , so at least that problem will be looked at and not fall through the cracks. – Elizabeth Mattijsen Apr 08 '17 at 17:03
  • 1
    So far I haven't been shy about making doc issues, but I also want to do my homework first. So, there's https://github.com/perl6/doc/issues/1274 and https://github.com/perl6/doc/issues/1275. – brian d foy Apr 09 '17 at 00:08