Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Distinguish between Exception and Failure in a CATCH block [ RAKU ]

We know that a Failure can be handled by a CATCH block.

In the following example we create an 'AdHoc' Failure (in other-sub) and we handle the Exception in a CATCH block (in my-sub)

sub my-sub {
    try {
        CATCH {
            when X::AdHoc { say 'AdHoc Exception handled here'; .resume }
            default {say 'Other Exception'; .resume}
        }

        my $b = other-sub();

        $b.so ?? $b.say !! 'This was a Failure'.say;
    }
}

sub other-sub { fail 'Failure_X' }

my-sub();

The output is the following:

AdHoc Exception handled here
This was a Failure

My question is though: How can we distinguish between Failure and a "normal" Exception in the CATCH block in order to differentiate between the two cases?

like image 767
jakar Avatar asked Jan 30 '20 09:01

jakar


2 Answers

The relationship between Failure and Exception is that a Failure has an Exception - that is to say, it holds the exception object as part of its state. Something like this:

class Failure {
    has Exception $.exception;
    # ...
}

When a Failure "explodes", it does so by throwing the Exception that is inside of it. Thus, what reaches the CATCH block is the Exception object, and there's no link back to the enclosing Failure. (In fact, a given Exception object could in principle be held by many Failures.)

Therefore, there's no direct way to detect this. From a design perspective, you probably shouldn't be, and should find a different way to solve your problem. A Failure is just a way to defer the throwing of an exception and allowing for it to be treated as a value; it's not intended that the nature of the underlying problem changes because it's conveyed as a value rather than as an immediate transfer of control flow. Unfortunately, the original goal wasn't stated in the question; you may find it useful to look at control exceptions, but otherwise perhaps post another question about the underlying problem you're trying to solve. There's probably a better way.

For completeness, I'll note that there are indirect ways that one may detect that the Exception was thrown by a Failure. For example, if you obtain the .backtrace of the exception object and look at the top frame's package, it's possible to determine that it comes from the Failure:

sub foo() { fail X::AdHoc.new(message => "foo") }
try {
    foo();
    CATCH {
        note do { no fatal; .backtrace[0].code.package ~~ Failure };
        .resume
    }
}

However, this is heavily dependent on implementation details that could easily change, so I'd not rely on it.

like image 80
Jonathan Worthington Avatar answered Nov 01 '22 05:11

Jonathan Worthington


Just remove the try wrapper:

sub my-sub {

#    try {              <--- remove this line...

        CATCH {
            when X::AdHoc { say 'AdHoc Exception handled here'; .resume }
            default {say 'Other Exception'; .resume}
        }

        my $b = other-sub();

        $b.so ?? $b.say !! 'This was a Failure'.say;

#    }                  <--- ...and this one

}

sub other-sub { fail 'Failure_X' }

my-sub();

You used try. A try does a few things, but the pertinent thing here is that it tells Raku to immediately promote any Failures in its scope to exceptions -- which is what you say you don't want. So the simplest solution is to just stop doing that.


This answer just verbosely repeats part of jnthn's explanation (see in particular comments he wrote below his answer). But I wasn't convinced all readers would spot/understand this aspect, and didn't think a comment or two on jnthn's answer would help, hence this answer.

I've written this as a community answer to ensure I won't benefit from any upvotes because it obviously doesn't warrant that. If it gets enough downvotes we'll just delete it.

like image 32
raiph Avatar answered Nov 01 '22 05:11

raiph